Skip to main content

jdc_core/
sese.rs

1//! From-scratch SESE / dominator-tree control-flow structurer.
2//!
3//! Replaces the heuristic `walk` with a monotone region decomposition:
4//! * follows are TRUE immediate post-dominators (computed once per method via a
5//!   sentinel-virtual-exit reverse CHK), fixing the compound-condition
6//!   `if (A || B) then; tail` mis-follow at the root;
7//! * every block is structured EXACTLY ONCE — guarded by a `consumed` set and a
8//!   "reached-from-this-region's-entry" check. A branch that runs into an
9//!   already-consumed block, or into a block it does not reach (a shared merge
10//!   owned by a sibling/follow), emits `Goto{block}` instead of re-walking it.
11//!   There is NO fresh-claimed shared-tail copy, so the decomposition cannot
12//!   diverge (the failure mode of every incremental patch on `walk`).
13//!
14//! Gated behind `JCDC_SESE`; `structure_method` falls back to `walk` when unset.
15//! Milestone 1: straight-line, Cond (if/else + value-diamond/ternary), natural
16//! loops. Switch/Try reuse the existing (pub(crate)) region builders next.
17
18use std::collections::{HashMap, HashSet};
19
20use crate::ir::build::Term;
21use crate::ir::expr::Expr;
22use crate::ir::stmt::Stmt;
23
24use crate::structure::{
25    compute_dominators, compute_postdominators, reachable_within, DomInfo,
26    Region, Structurer,
27};
28
29// Per-method precomputation for the SESE decomposition.
30thread_local! {
31    /// Set when a matexit splice committed in the current method; read by
32    /// decompile_method's post-pipeline pathology retry.
33    pub static MATEXIT_FIRED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
34    /// Method-local kill switch for the retry pass.
35    pub static MATEXIT_DISABLED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
36}
37
38struct SeseCtx {
39    universe: HashSet<usize>,
40    idom: DomInfo,
41    /// block -> immediate post-dominator (== vx when only the virtual exit).
42    ipdom: HashMap<usize, usize>,
43    /// sentinel virtual-exit id.
44    vx: usize,
45    /// loop headers (back-edge targets that dominate the source).
46    loop_headers: HashSet<usize>,
47    /// loop headers whose back edge is EXCEPTION-mediated (the retry jump
48    /// originates in handler flow, invisible to normal dominance at the
49    /// group-yield check).
50    exc_retry_headers: HashSet<usize>,
51    /// header -> natural-loop member set.
52    loop_members: HashMap<usize, HashSet<usize>>,
53    /// blocks consumed (structured exactly once) across the whole method.
54    consumed: HashSet<usize>,
55    /// loop headers currently being structured (a back-edge to one is a
56    /// `continue`, never a copy_walk target).
57    loop_stack: Vec<usize>,
58    /// Outermost try groups (mirrors walk's `top_groups`): the `active`
59    /// candidate list threaded into walk-based sub-builders so a try group
60    /// that STARTS inside a switch case or a copied tail still fires there
61    /// (URL$DefaultFactory.createURLStreamHandler: the reflection try lives
62    /// entirely inside the stage-2 switch default case; with active=[] the
63    /// case walk structured it bare and the catch was orphaned -> unreported
64    /// checked exceptions, a jdk11/17 corpus blocker).
65    top_groups: Vec<usize>,
66    /// recursion depth (safety bound).
67    depth: usize,
68}
69
70impl<'a> Structurer<'a> {
71    /// Structure the whole method via SESE decomposition.
72    /// Catch-and-retry header detection shared by both structurizers.
73    /// A catch-and-retry handler has NO normal-flow preds, so the
74    /// dominator tree roots it and its `goto header` back edge is
75    /// invisible to natural-loop detection. Returns the per-header
76    /// handler-flow back-edge sources and (as a side effect) marks the
77    /// headers in `loop_headers` and `self.sese_exc_retry_headers`.
78    /// The WALK structurer consumes sese_exc_retry_headers (its
79    /// loop-structuring branch + single_enclosing_succ) — in walk-only
80    /// mode it was never computed, so exc-mediated retry loops unrolled
81    /// into nested try copies (jdk11/17 ObjectInputStream.auditSubclass
82    /// lost the retry loop and its tail return — 缺少返回语句 x2 trees;
83    /// SESE rendered it correctly).
84    pub(crate) fn precompute_exc_retry(
85        &mut self,
86        universe: &HashSet<usize>,
87        idom: &crate::structure::DomInfo,
88        loop_headers: &mut HashSet<usize>,
89    ) -> HashMap<usize, Vec<usize>> {
90        let mut exc_back_sources: HashMap<usize, Vec<usize>> = HashMap::new();
91        let mut exc_retry_only: HashSet<usize> = HashSet::new();
92        for e in &self.cfg.exc_edges {
93            if !(universe.contains(&e.from) && universe.contains(&e.to)) {
94                continue;
95            }
96            let c = e.to;
97            if !self.cfg.blocks[c].pred.is_empty() {
98                continue; // normally reachable: real dominators apply
99            }
100            // Handler-flow closure: blocks whose normal preds all stay
101            // within the handler flow. The retry back edge may originate
102            // deeper than the handler entry (`catch { if (rem > 0)
103            // sleep(..); rem = ..; if (rem > 0) goto head; }` — jdk11
104            // Process.waitFor: the back edge sits at the while-test two
105            // hops down; the direct-succ scan missed it, the retry loop
106            // never structured, and the back-edge arrival copy_walked the
107            // try head into 5 nested duplicate tries with the final
108            // `return false` lost — 缺少返回语句). Blocks with a normal pred
109            // from OUTSIDE the handler flow (shared merges) stop the
110            // closure; they belong to the enclosing flow.
111            let mut hf: HashSet<usize> = HashSet::new();
112            hf.insert(c);
113            {
114                let mut q: std::collections::VecDeque<usize> = std::collections::VecDeque::new();
115                q.push_back(c);
116                while let Some(b) = q.pop_front() {
117                    for &s in &self.cfg.blocks[b].succ {
118                        if hf.contains(&s)
119                            || !universe.contains(&s)
120                            || self.handler_group.contains_key(&s)
121                        {
122                            continue;
123                        }
124                        if self.cfg.blocks[s]
125                            .pred
126                            .iter()
127                            .all(|p| hf.contains(p) || self.handler_group.contains_key(p))
128                        {
129                            hf.insert(s);
130                            q.push_back(s);
131                        }
132                    }
133                }
134            }
135            for &b in hf.iter() {
136                for &h in &self.cfg.blocks[b].succ {
137                    if hf.contains(&h) {
138                        continue;
139                    }
140                    if universe.contains(&h) && idom.dominates(h, e.from) {
141                        loop_headers.insert(h);
142                        exc_retry_only.insert(h);
143                        let srcs = exc_back_sources.entry(h).or_default();
144                        for &m in hf.iter() {
145                            if !srcs.contains(&m) {
146                                srcs.push(m);
147                            }
148                        }
149                    }
150                }
151            }
152        }
153        if crate::dbg_flag!("JCDC_DBG_IF") && !exc_retry_only.is_empty() {
154            let mut v: Vec<usize> = exc_retry_only.iter().copied().collect();
155            v.sort_unstable();
156            eprintln!("EXCRETRY headers={:?}", v);
157        }
158        self.sese_exc_retry_headers = exc_retry_only;
159        exc_back_sources
160    }
161
162    pub fn structure_method_sese(&mut self) -> Region {
163        let universe: HashSet<usize> = self
164            .cfg
165            .blocks
166            .iter()
167            .filter(|b| b.ins_len != 0)
168            .map(|b| b.id)
169            .collect();
170        let idom = compute_dominators(self.cfg, &universe, self.cfg.entry);
171        let (vx, ipdom) = compute_postdominators(self.cfg, &universe);
172
173        // Natural loops from back edges (h dominates u => u->h is a back edge).
174        let mut loop_headers: HashSet<usize> = HashSet::new();
175        for &u in universe.iter() {
176            for &s in &self.cfg.blocks[u].succ {
177                if universe.contains(&s) && s != u && idom.dominates(s, u) {
178                    loop_headers.insert(s);
179                }
180                if s == u {
181                    loop_headers.insert(u); // self loop
182                }
183            }
184        }
185        let exc_back_sources = self.precompute_exc_retry(&universe, &idom, &mut loop_headers);
186        // Exception-edge predecessors: handler -> protected blocks. A protected
187        // block reaches its handler only on a throw (an exception edge, not a
188        // normal pred), so when the handler loops back to the header, the
189        // protected block is part of the loop too. Without this, a
190        // `while(..){ try{ return }catch{ ..loop.. } }` drops the try out of
191        // the loop (the SecureRandom.getInstanceStrong regression).
192        let mut exc_preds: HashMap<usize, Vec<usize>> = HashMap::new();
193        for e in &self.cfg.exc_edges {
194            if universe.contains(&e.from) && universe.contains(&e.to) {
195                exc_preds.entry(e.to).or_default().push(e.from);
196            }
197        }
198        let mut loop_members: HashMap<usize, HashSet<usize>> = HashMap::new();
199        for &h in loop_headers.iter() {
200            let mut members: HashSet<usize> = HashSet::new();
201            members.insert(h);
202            // Collect every TRUE back-edge source for h (u -> h where h
203            // dominates u; without the dominance check a forward entry edge
204            // like a pre-loop init guard -> header pollutes the member set
205            // and the whole enclosing chain gets swallowed, Integer blk10),
206            // then walk predecessors (normal AND exception) within the
207            // universe until reaching h.
208            let mut stack: Vec<usize> = Vec::new();
209            for &u in universe.iter() {
210                let is_self_loop = u == h && self.cfg.blocks[h].succ.contains(&h);
211                let is_back_edge = u != h && idom.dominates(h, u);
212                if self.cfg.blocks[u].succ.contains(&h) && (is_self_loop || is_back_edge) {
213                    if members.insert(u) {
214                        stack.push(u);
215                    }
216                }
217            }
218            if let Some(srcs) = exc_back_sources.get(&h) {
219                for &c in srcs {
220                    if members.insert(c) {
221                        stack.push(c);
222                    }
223                }
224            }
225            while let Some(b) = stack.pop() {
226                // Do NOT traverse the header's own predecessors: the natural
227                // loop is {h} u {nodes reaching a back-edge source without
228                // going through h}. Walking h's preds would pull in the
229                // pre-loop code (entry, guards) and duplicate it inside the
230                // body region.
231                if b == h {
232                    continue;
233                }
234                for &p in &self.cfg.blocks[b].pred {
235                    if universe.contains(&p) && members.insert(p) {
236                        stack.push(p);
237                    }
238                }
239                if let Some(xp) = exc_preds.get(&b) {
240                    for &p in xp {
241                        if members.insert(p) {
242                            stack.push(p);
243                        }
244                    }
245                }
246            }
247            loop_members.insert(h, members);
248        }
249
250        let top_groups: Vec<usize> = (0..self.groups.len())
251            .filter(|gi| {
252                let g = &self.groups[*gi];
253                !self.groups.iter().enumerate().any(|(oj, og)| {
254                    oj != *gi
255                        && og.start <= g.start
256                        && og.end >= g.end
257                        && (og.start != g.start || og.end != g.end)
258                })
259            })
260            .collect();
261        self.sese_loop_headers = loop_headers.clone();
262        let mut ctx = SeseCtx {
263            universe,
264            idom,
265            ipdom,
266            vx,
267            exc_retry_headers: exc_back_sources.keys().copied().collect(),
268            loop_headers,
269            loop_members,
270            consumed: HashSet::new(),
271            loop_stack: Vec::new(),
272            top_groups,
273            depth: 0,
274        };
275        let r = self.sese_region(self.cfg.entry, &HashSet::new(), &mut ctx);
276        r
277    }
278
279    /// Immediate post-dominator of `b` (None if only the virtual exit, i.e. all
280    /// paths from b leave the universe / return / throw).
281    fn sese_ipdom(&self, ctx: &SeseCtx, b: usize) -> Option<usize> {
282        match ctx.ipdom.get(&b).copied() {
283            Some(p) if p != ctx.vx && p != b => Some(p),
284            _ => None,
285        }
286    }
287
288    /// Convergent merge of a set of branch/case targets within a region: the
289    /// nearest block (by linear layout) forward-reachable from ALL targets,
290    /// not in `stop`, and not yet consumed. This recovers a shared
291    /// continuation that is NOT a post-dominator because the targets diverge
292    /// to different terminals (a loop `break` -> exit, a `return`/`throw`, or
293    /// a loop tail -> header back-edge that only post-dominates at the virtual
294    /// exit). Examples: the loop tail after `if (a==2 && b==2) break outer;`
295    /// (Legacy6), and the shared `++i` tail after every `case` of a switch
296    /// inside a loop (EnumSwitch — without it the first case swallows the tail
297    /// as `++i; continue` and the remaining cases spin forever).
298    fn convergent_merge(
299        &self,
300        ctx: &SeseCtx,
301        targets: &[usize],
302        stop: &HashSet<usize>,
303    ) -> Option<usize> {
304        if targets.is_empty() {
305            return None;
306        }
307        // Floor = the branch block's own position, NOT max(target starts): a
308        // branch target may sit EARLIER in the layout than the branch (a
309        // backward jump to a fixup, Integer.toString C1 -> FIXUP), and a
310        // max-of-targets floor then hides the true nearest confluence and
311        // lets a far block inside the branch body be chosen as follow.
312        let floor = targets
313            .iter()
314            .filter_map(|&t| self.cfg.blocks[t].pred.iter().map(|&p| self.cfg.blocks[p].start as usize).max())
315            .max()
316            .unwrap_or(0);
317        // Multi-block condition chains (`while (c1 || c2) body;` compiled as
318        // H1: if c1 goto BODY; H2: if c2 goto BODY; else EXIT; BODY: ..; goto
319        // H1): the confluence of H1's branches is the NEXT TEST block, which
320        // is itself one of the targets. Prefer it before the general scan —
321        // the dominance guards below would (correctly in general) reject it,
322        // but here the sibling target IS the chain continuation; picking it
323        // flattens the body to `if (c1) continue; if (c2) continue; else
324        // exit;`, the shape extract_compound_do_while / the CLASSIFY2 fold
325        // consume (jdk8 Random.internalNextInt). Legacy6 is unaffected: its
326        // fall target (the tail) is reachable from the sibling, so the
327        // sibling is not a confluence there.
328        for &t in targets.iter() {
329            if stop.contains(&t)
330                || ctx.consumed.contains(&t)
331                || ctx.loop_stack.contains(&t)
332            {
333                if crate::dbg_flag!("JCDC_DBG_LM") {
334                    eprintln!("CM-SKIP t={} stop={} consumed={} lstack={} targets={:?}", t,
335                        stop.contains(&t), ctx.consumed.contains(&t), ctx.loop_stack.contains(&t), targets);
336                }
337                continue;
338            }
339            if targets
340                .iter()
341                .all(|&o| o == t || self.reaches_within(ctx, o, t, stop))
342            {
343                if crate::dbg_flag!("JCDC_DBG_LM") {
344                    eprintln!("CM-RET {} targets={:?}", t, targets);
345                }
346                return Some(t);
347            }
348        }
349        let mut best: Option<usize> = None;
350        let mut best_start = usize::MAX;
351        for &x in ctx.universe.iter() {
352            let xs = self.cfg.blocks[x].start as usize;
353            if xs < floor || xs >= best_start {
354                continue;
355            }
356            if stop.contains(&x) || ctx.consumed.contains(&x) {
357                continue;
358            }
359            // A loop header is never a merge candidate WHILE INSIDE its
360            // loop: a branch that jumps to it is a `continue` (the back
361            // edge fires on the NEXT iteration), not a fall-through
362            // confluence. During a body walk the header sits on the
363            // loop_stack, so this guard keeps StringUTF16.codePointCount's
364            // `continue`s. OUTSIDE the loop (switch cases that all
365            // `goto` a loop head laid out after the switch — jdk11
366            // Subject.populateSet) the header IS the switch follow: the
367            // cases break into it and the loop structures ONCE after the
368            // switch instead of being copy-duplicated into every case.
369            if ctx.loop_stack.contains(&x) {
370                continue;
371            }
372            // A candidate that DOMINATES ANY branch target (reflexively: is
373            // a target) is an ancestor
374            // confluence (a fixup block the branches jump back/up to, e.g.
375            // `if (A || B) radix = 10;` where the fixup dominates the cond),
376            // not a follow: choosing it empties a branch and loses the
377            // post-fixup continuation (Integer.toString). Such shapes need
378            // follow=None so the branches are walked naturally and the
379            // re-reached continuation is duplicated by copy_walk (walk
380            // parity). Genuine shared tails (Legacy6/EnumSwitch) are
381            // dominated BY their targets, never dominate them.
382            if targets.iter().any(|&t| t != x && ctx.idom.dominates(x, t)) {
383                continue;
384            }
385            // ... and reject a candidate that IS a branch target while
386            // ANOTHER target sits strictly above it in the dominator tree
387            // (`if (A || B) fixup; main;` — fixup dominates main): choosing
388            // the lower target as follow empties the jump branch and orphans
389            // the fixup (Integer.toString C2 -> empty `if (radix <= 36)`).
390            // A genuine shared tail (Legacy6) is dominated BY its sibling
391            // branch, not the other way around, so it survives both guards.
392            if targets.contains(&x)
393                && targets
394                    .iter()
395                    .any(|&t| t != x && ctx.idom.dominates(t, x))
396            {
397                continue;
398            }
399            if targets.iter().all(|&t| self.reaches_within(ctx, t, x, stop)) {
400                best = Some(x);
401                best_start = xs;
402            }
403        }
404        best
405    }
406
407    /// Can `from` reach `target` stepping only through blocks not in `stop`
408    /// (target itself may be in `stop` — it is the destination), AND without
409    /// re-entering an enclosing loop? Reaching a loop header on `loop_stack`
410    /// is a back-edge (loop re-entry): anything past it is a *later iteration*,
411    /// not a forward merge. Restricting to forward reach is what lets
412    /// convergent_merge recover a shared loop tail (Legacy6: B2 -> TAIL is a
413    /// forward edge) while refusing to invent a follow across a `continue`
414    /// (ControlFlow.nestedLoops: the `++j;continue` block only reaches `++c`
415    /// via the header back-edge, so `++c` is correctly NOT a merge).
416    /// Nearest block (by start) that every NON-TERMINATING successor
417    /// reaches within `stop` — unlike convergent_merge this MAY return a
418    /// stop member: a switch/if inside a loop body whose branches break
419    /// to the loop increment (a body_stop exit) still has a well-defined
420    /// merge; refusing it strands breaks as RawGotos and drops shared
421    /// tails (ObjectStreamClass.computeFieldOffsets, Long$LongCache
422    /// clinit). Cm's dominance guards still apply: a candidate that
423    /// (properly) dominates a live target is an ancestor confluence
424    /// (Integer.toString fixup), not a follow.
425    fn live_merge(
426        &self,
427        ctx: &SeseCtx,
428        succs: &[usize],
429        stop: &HashSet<usize>,
430    ) -> Option<usize> {
431        let live: Vec<usize> = succs
432            .iter()
433            .copied()
434            .filter(|&t| !matches!(self.results[t].term, Term::Return(_) | Term::Throw(_)))
435            .collect();
436        // Require 2+ live branches: a single live target trivially
437        // "merges with itself" — for a loop condition that makes the
438        // body entry the follow, emptying the else branch and degrading
439        // ControlFlow.breakInLoop's `return i` to `break`. One-live
440        // shapes must keep follow=None so branches structure naturally.
441        if live.len() < 2 {
442            return None;
443        }
444        // A branch target that every live branch flows to IS the merge
445        // (the sibling completes normally into it and the branch is an
446        // empty fallthrough) — prefer it even when it is a shared
447        // terminator; scanning by nearest-start would otherwise pick a
448        // non-target confluence and strand the tail (Fin/Long$LongCache:
449        // `cache = archivedCache; return` must be the follow, not a
450        // branch-body copy with the top-level tail lost). ONLY with 2+
451        // live targets: a loop condition (one live body entry, one exit
452        // branch) must keep follow=None so both branches structure
453        // naturally — otherwise the body entry becomes the "follow", the
454        // else branch empties, and ControlFlow.breakInLoop's `return i`
455        // degrades to `break`.
456        if live.len() >= 2 {
457            for &t in succs.iter() {
458                if stop.contains(&t) {
459                    if crate::dbg_flag!("JCDC_DBG_LM") {
460                        eprintln!("LM-SKIP t={} stop succs={:?}", t, succs);
461                    }
462                    continue;
463                }
464                if ctx.consumed.contains(&t) {
465                    if crate::dbg_flag!("JCDC_DBG_LM") {
466                        eprintln!("LM-SKIP t={} consumed succs={:?}", t, succs);
467                    }
468                    continue;
469                }
470                if live
471                    .iter()
472                    .all(|&o| o == t || self.reaches_within(ctx, o, t, stop))
473                {
474                    if crate::dbg_flag!("JCDC_DBG_LM") {
475                        eprintln!("LM-RET {} succs={:?} live={:?}", t, succs, live);
476                    }
477                    return Some(t);
478                }
479            }
480        }
481        let mut best: Option<usize> = None;
482        for &cand in ctx.universe.iter() {
483            if ctx.consumed.contains(&cand) || ctx.loop_stack.contains(&cand) {
484                continue;
485            }
486            // (Shared terminators ARE eligible here: a branch merging at
487            // a return block is a normal follow — Long$LongCache's tail.
488            // The escape filter downstream in the follow chain rejects
489            // the cases where a branch jumps OUT instead, jdk17 String.)
490            if live.iter().any(|&t| t != cand && ctx.idom.dominates(cand, t)) {
491                continue;
492            }
493            if live.contains(&cand)
494                && live.iter().any(|&t| t != cand && ctx.idom.dominates(t, cand))
495            {
496                continue;
497            }
498            if live
499                .iter()
500                .all(|&t| t == cand || self.reaches_within(ctx, t, cand, stop))
501            {
502                match best {
503                    None => best = Some(cand),
504                    Some(b) if self.cfg.blocks[cand].start < self.cfg.blocks[b].start => {
505                        best = Some(cand)
506                    }
507                    _ => {}
508                }
509            }
510        }
511        best
512    }
513
514    /// True when `cur` (a stop member) flows through single-successor
515    /// blocks to an already-consumed terminator: the chain is safe to
516    /// copy here because its endpoint was structured elsewhere and the
517    /// copy is this path's sole continuation.
518    fn sese_stop_chain_to_claimed_terminator(
519        &self,
520        cur: usize,
521        stop: &HashSet<usize>,
522        ctx: &SeseCtx,
523    ) -> bool {
524        let mut x = cur;
525        for _ in 0..8 {
526            if matches!(self.results[x].term, Term::Return(_) | Term::Throw(_)) {
527                // Statement-bearing claimed terminators are the post-loop
528                // continuation tail (shared clinit tails get claimed by the
529                // first arrival), NOT absorbed restart-loop scaffolding —
530                // copying them into the exit test duplicates blank-final
531                // assignments inside the loop (SecurityProviderConstants
532                // 可能在 loop 中分配了变量 x18). See the walk-side twin.
533                return ctx.consumed.contains(&x) && self.results[x].stmts.is_empty();
534            }
535            if !matches!(self.results[x].term, Term::Fallthrough | Term::Goto) {
536                return false;
537            }
538            if !self.results[x].stmts.is_empty() && !ctx.consumed.contains(&x) {
539                return false;
540            }
541            let succs = &self.cfg.blocks[x].succ;
542            if succs.len() != 1 {
543                return false;
544            }
545            let n = succs[0];
546            if n == x || ctx.loop_stack.contains(&n) {
547                return false;
548            }
549            let _ = stop;
550            x = n;
551        }
552        false
553    }
554
555    /// Loop-tail switch follow recovery. A switch inside a loop whose cases
556    /// either `break` the SWITCH into a shared epilogue that flows back to
557    /// the header, exit the LOOP (stop), `continue` (header), or terminate:
558    /// ipdom post-dominates only at the virtual exit and the forward-merge
559    /// resolvers refuse header-reaching paths, so follow=None — the epilogue
560    /// is consumed by the first case walk that arrives, sibling cases lose
561    /// their `break`, and the post-loop continuation is stranded (jdk26
562    /// Pattern.sequence/expr: lost `node = closure(node)` epilogue and
563    /// post-loop `if (head == null) return end; root = tail; return head;`
564    /// → 缺少返回语句). E is the nearest block every case target SETTLES at:
565    /// each path from each target reaches E forward (no stop/header steps),
566    /// escapes to a stop (outer break), continues into an enclosing loop
567    /// header, or terminates (return/throw).
568    fn loop_tail_switch_follow(
569        &self,
570        ctx: &SeseCtx,
571        cur: usize,
572        stop: &HashSet<usize>,
573    ) -> Option<usize> {
574        if ctx.loop_stack.is_empty() {
575            return None;
576        }
577        let targets: Vec<usize> = self.cfg.blocks[cur].succ.clone();
578        if targets.is_empty() {
579            return None;
580        }
581        // Candidates: the DESTINATIONS of case-terminal transfers only —
582        // each case region's trailing `goto X` targets and fallthrough
583        // successors whose source block has multiple in-region succ paths
584        // (a full forward closure would offer every case-internal block,
585        // and a private `continue` stub settling on all paths could win
586        // the start-pc contest: Pattern.sequence case 40's `goto 4` block).
587        let mut cands: Vec<usize> = Vec::new();
588        {
589            let mut seen: HashSet<usize> = HashSet::new();
590            let mut stack: Vec<usize> = targets.clone();
591            let mut visited: HashSet<usize> = HashSet::new();
592            while let Some(b) = stack.pop() {
593                if stop.contains(&b) || ctx.loop_stack.contains(&b) || !visited.insert(b) {
594                    continue;
595                }
596                let succs = &self.cfg.blocks[b].succ;
597                match self.results[b].term {
598                    crate::ir::build::Term::Goto => {
599                        // A case-terminal jump: its destination is a
600                        // break/continue/epilogue candidate.
601                        if let Some(&t) = succs.first() {
602                            if !targets.contains(&t)
603                                && !ctx.loop_stack.contains(&t)
604                                && !stop.contains(&t)
605                                && seen.insert(t)
606                            {
607                                cands.push(t);
608                            }
609                        }
610                    }
611                    crate::ir::build::Term::Fallthrough => {
612                        if let Some(&t) = succs.first() {
613                            // Fallthrough into a block with preds from
614                            // ELSEWHERE in the switch (a shared epilogue
615                            // head): candidate. Purely linear flow keeps
616                            // walking (the case's own body).
617                            let shared = self.cfg.blocks[t].pred.iter().any(|&p| {
618                                p != b && (targets.contains(&p) || visited.contains(&p))
619                            });
620                            if shared
621                                && !targets.contains(&t)
622                                && !ctx.loop_stack.contains(&t)
623                                && !stop.contains(&t)
624                                && seen.insert(t)
625                            {
626                                cands.push(t);
627                            }
628                            stack.push(t);
629                        }
630                    }
631                    crate::ir::build::Term::Cond { .. } | crate::ir::build::Term::Switch { .. } => {
632                        stack.extend(succs.iter().copied());
633                    }
634                    // A case body can END in straight-line fallthrough into
635                    // the shared epilogue (Pattern.sequence case 92 flows
636                    // into the `node = closure(node)` tail): keep walking
637                    // non-branching blocks so its destination surfaces.
638                    _ if succs.len() == 1 && !matches!(self.results[b].term,
639                        crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)) => {
640                        stack.extend(succs.iter().copied());
641                    }
642                    // Return/Throw: no continuation candidate.
643                    _ => {}
644                }
645            }
646        }
647        let mut best: Option<usize> = None;
648        let mut best_score: Option<(usize, u32)> = None;
649        for &e in &cands {
650            if stop.contains(&e) || ctx.consumed.contains(&e) {
651                continue;
652            }
653            // E must flow back to an enclosing loop header (it IS the loop
654            // tail) — otherwise the switch-break confluence is a plain
655            // post-switch block the forward resolvers would have found.
656            let mut to_header = false;
657            {
658                let mut seen: HashSet<usize> = HashSet::new();
659                let mut stack: Vec<usize> = vec![e];
660                while let Some(b) = stack.pop() {
661                    if !seen.insert(b) {
662                        continue;
663                    }
664                    for &s in &self.cfg.blocks[b].succ {
665                        if ctx.loop_stack.contains(&s) {
666                            to_header = true;
667                            break;
668                        }
669                        if !stop.contains(&s) {
670                            stack.push(s);
671                        }
672                    }
673                    if to_header {
674                        break;
675                    }
676                }
677            }
678            if !to_header {
679                continue;
680            }
681            if targets
682                .iter()
683                .all(|&t| self.settles_at(ctx, stop, t, e, 0))
684            {
685                // Score = how many DISTINCT case targets reach `e` forward:
686                // the switch-break confluence collects every breaking case,
687                // while a case-private merge/continue stub is reached only
688                // by its own case (Pattern.sequence b7 [case 40's
689                // `tail = root; goto EPI`] vs the true epilogue EPI which
690                // all eight breaking cases flow into). Ties: nearest by
691                // start pc (the epilogue's own head block).
692                let score = targets
693                    .iter()
694                    .filter(|&&t| self.reaches_fwd(ctx, stop, t, e, 0))
695                    .count();
696                best = match best_score {
697                    None => {
698                        best_score = Some((score, self.cfg.blocks[e].start));
699                        Some(e)
700                    }
701                    Some((bs, bstart)) => {
702                        let better = score > bs
703                            || (score == bs && self.cfg.blocks[e].start < bstart);
704                        if better {
705                            best_score = Some((score, self.cfg.blocks[e].start));
706                            Some(e)
707                        } else {
708                            best
709                        }
710                    }
711                };
712            }
713        }
714        best
715    }
716
717    /// Some forward path from `t` reaches `e` without stepping on stop
718    /// members or enclosing loop headers.
719    fn reaches_fwd(
720        &self,
721        ctx: &SeseCtx,
722        stop: &HashSet<usize>,
723        t: usize,
724        e: usize,
725        depth: usize,
726    ) -> bool {
727        if depth > 4096 {
728            return false;
729        }
730        if t == e {
731            return true;
732        }
733        if stop.contains(&t) || ctx.loop_stack.contains(&t) {
734            return false;
735        }
736        self.cfg.blocks[t]
737            .succ
738            .iter()
739            .any(|&s| self.reaches_fwd(ctx, stop, s, e, depth + 1))
740    }
741
742    /// Every path from `t` settles at `e`: reaches it forward, escapes to a
743    /// stop (outer break), continues into an enclosing loop header, or
744    /// terminates (return/throw). See `loop_tail_switch_follow`.
745    fn settles_at(
746        &self,
747        ctx: &SeseCtx,
748        stop: &HashSet<usize>,
749        t: usize,
750        e: usize,
751        depth: usize,
752    ) -> bool {
753        if depth > 4096 {
754            return false;
755        }
756        if t == e {
757            return true;
758        }
759        if stop.contains(&t) || ctx.loop_stack.contains(&t) {
760            return true; // outer break / continue: structured elsewhere
761        }
762        let succs = &self.cfg.blocks[t].succ;
763        if succs.is_empty() {
764            return matches!(
765                self.results[t].term,
766                crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
767            );
768        }
769        succs.iter().all(|&s| self.settles_at(ctx, stop, s, e, depth + 1))
770    }
771
772    /// Stronger sibling check for a branch-target follow candidate: EVERY
773    /// bounded route from `from` passes through `target` or leaves abruptly
774    /// (return/throw/stop-jump/loop re-entry). Used ONLY as the trigger
775    /// for the false-merge reconciliation below (never as a standalone
776    /// follow filter — the documented-failed variants 1-7 in memory
777    /// ocsp-sese-follow-diagnosis rejected follows outright and traded
778    /// OCSPResponse against BasicImageReader/keytool/Locale/Throwable/
779    /// ConcurrentHashMap families): a sibling route that completes
780    /// normally PAST the parked target's emission point is the crossing
781    /// that makes the empty-arm contract unsound.
782    fn all_routes_via(
783        &self,
784        ctx: &SeseCtx,
785        from: usize,
786        target: usize,
787        stop: &HashSet<usize>,
788    ) -> bool {
789        if from == target {
790            return true;
791        }
792        let mut post = crate::structure::reachable_within(self.cfg, target, stop);
793        post.remove(&target);
794        let mut seen: HashSet<usize> = HashSet::new();
795        let mut stack: Vec<usize> = vec![from];
796        let mut budget = 4096usize;
797        while let Some(c) = stack.pop() {
798            if c == target || seen.contains(&c) {
799                continue;
800            }
801            if budget == 0 {
802                return false;
803            }
804            budget -= 1;
805            seen.insert(c);
806            if post.contains(&c) {
807                return false; // bypass into the post-target flow
808            }
809            if stop.contains(&c) || ctx.loop_stack.contains(&c) {
810                continue; // abrupt escape: jump/continue, never falls through
811            }
812            if matches!(
813                self.results[c].term,
814                crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
815            ) {
816                continue;
817            }
818            let succs: Vec<usize> = self.cfg.blocks[c]
819                .succ
820                .iter()
821                .copied()
822                .filter(|&sx| ctx.universe.contains(&sx))
823                .collect();
824            if succs.is_empty() {
825                continue; // leaves the region: abrupt from this If's view
826            }
827            for sx in succs {
828                if ctx.loop_headers.contains(&sx) && !seen.contains(&sx) {
829                    // Re-entry into a loop header is a back-edge jump
830                    // (continue), not a fallthrough — abrupt.
831                    if self.cfg.blocks[c].succ.iter().any(|&o| o == sx) && sx != target {
832                        if self.cfg.blocks[sx].start <= self.cfg.blocks[c].start {
833                            continue;
834                        }
835                    }
836                }
837                stack.push(sx);
838            }
839        }
840        true
841    }
842
843    fn reaches_within(&self, ctx: &SeseCtx, from: usize, target: usize, stop: &HashSet<usize>) -> bool {
844        if from == target {
845            return true;
846        }
847        // A branch that jumps straight to a stop block LEAVES the region
848        // (sibling/follow owns it); it contributes no in-region confluence.
849        // Without this, Integer's C2 (`radix <= 36`) "reaches" the subtree
850        // through the fixup stop in one hop and cm picks the subtree head as
851        // follow, emptying the branch and losing the fixup+copy shape.
852        if stop.contains(&from) {
853            return false;
854        }
855        let mut seen = HashSet::new();
856        let mut stack = vec![from];
857        seen.insert(from);
858        while let Some(c) = stack.pop() {
859            for &s in &self.cfg.blocks[c].succ {
860                if s == target {
861                    return true;
862                }
863                // Not expandable: stop blocks (region boundaries — a sibling
864                // branch owns them; routing through one is not a confluence of
865                // THIS branch set: Integer C2 whose fixup target is the C1
866                // follow must not "reach" the subtree through it) and loop
867                // headers (re-entering a loop body is a later iteration, not a
868                // forward merge: ControlFlow.nestedLoops).
869                if stop.contains(&s)
870                    || ctx.loop_stack.contains(&s)
871                    || ctx.loop_headers.contains(&s)
872                {
873                    continue;
874                }
875                if seen.insert(s) {
876                    stack.push(s);
877                }
878            }
879        }
880        false
881    }
882
883    /// Structure the region starting at `entry`, stopping at any block in
884    /// `stop` (region exits / follows / enclosing barriers) or at the first
885    /// block not reached-from-`entry`-without-stop (a shared merge owned by a
886    /// sibling or the follow). Each block is consumed exactly once.
887    fn sese_region(
888        &mut self,
889        entry: usize,
890        stop: &HashSet<usize>,
891        ctx: &mut SeseCtx,
892    ) -> Region {
893        let reach = reachable_within(self.cfg, entry, stop);
894        self.sese_region_with_scope(entry, stop, reach, ctx)
895    }
896
897    /// Region walk with an explicit scope (`reach`). Normally the scope is
898    /// reachable_within(entry, stop); loop bodies pass the same set computed
899    /// once at the loop site so nested walks/copies share one bound.
900    /// Loop-level materialization of statement-bearing exits (jdk17
901    /// AQS.cleanQueue's dropped CAS-unlink phase). Runs AFTER the loop
902    /// body region is fully built, so the body's claims/consumed history
903    /// is untouched — the arm-site copy experiment perturbed sibling
904    /// structuring mid-walk (cancelAcquire regressed, cleanQueue's
905    /// advance path was swallowed) and was reverted; this pass is pure
906    /// region-tree post-processing. Each exit E carrying statement/branch
907    /// CONTENT (exit_has_content; stub-chain exits like Pattern.clazz's
908    /// switch-case goto stubs are exempt and keep their bare breaks) gets
909    /// its cascade structured ONCE on a clean barriered scope (all exits
910    /// + loop members + enclosing headers), then every `Goto{E}` in the
911    /// body becomes `Seq[cascade-copy, Goto{E}]`: the break keeps its
912    /// conversion binding while the cascade runs before it — the golden
913    /// shape (CAS unlink executes, then break restarts the outer loop).
914    /// Cascades whose flow ends at another exit or an enclosing back edge
915    /// render that jump as the matching break/continue (conversion
916    /// resolves against the enclosing loop stack; a cascade reaching an
917    /// UNENCLOSED header falls back to the bare break).
918    pub(crate) fn materialize_content_exits(
919        &mut self,
920        body: &mut Region,
921        header: usize,
922        exits: &[usize],
923        members: &HashSet<usize>,
924        natural: &HashSet<usize>,
925        ctx_universe: &HashSet<usize>,
926        ctx_loop_stack: &[usize],
927        ctx_loop_headers: &HashSet<usize>,
928        ctx_top_groups: &[usize],
929        outer_stop: &HashSet<usize>,
930        reach: &HashSet<usize>,
931        consumed: &HashSet<usize>,
932        natural_follow_empty: bool,
933    ) {
934        if MATEXIT_DISABLED.with(|d| d.get()) {
935            return;
936        }
937        let mut barriers: HashSet<usize> = exits.iter().copied().collect();
938        barriers.extend(members.iter().copied());
939        barriers.extend(ctx_loop_stack.iter().copied());
940        barriers.extend(ctx_loop_headers.iter().copied());
941        // PARENT-CONTINUATION fold: replicate the post-loop follow_pick's
942        // minimal candidate (lowest-start unconsumed in-reach exit) — when
943        // natural_follow is empty the parent continues at THAT exit and
944        // renders its flow once; materializing it double-renders the
945        // territory (ois readSerialData e=45: the cleanup-continuation
946        // copy re-bound catch identities downstream, markException(null)).
947        let mut natural: HashSet<usize> = natural.iter().copied().collect();
948        if natural_follow_empty {
949            // Full follow_pick replication (incl. the continue-stub
950            // filter): the parent's post-loop continuation is exactly
951            // this block, parent-rendered once — never materialize it.
952            let claim_cands: HashSet<usize> = exits
953                .iter()
954                .copied()
955                .filter(|e| {
956                    !consumed.contains(e)
957                        && !outer_stop.contains(e)
958                        && ctx_universe.contains(e)
959                        && reach.contains(e)
960                        && !self.is_handler(*e)
961                        && *e != header
962                })
963                .collect();
964            let mut cands: Vec<usize> = exits
965                .iter()
966                .copied()
967                .filter(|e| {
968                    !consumed.contains(e)
969                        && !outer_stop.contains(e)
970                        && ctx_universe.contains(e)
971                        && reach.contains(e)
972                        && !self.is_handler(*e)
973                        && *e != header
974                        && !ctx_loop_stack.iter().any(|h| {
975                            h != e
976                                && self.is_stmt_free_chain_to_block(*e, *h)
977                                && !self.stub_chain_claims_continuation(
978                                    *e,
979                                    *h,
980                                    &claim_cands,
981                                )
982                        })
983                })
984                .collect();
985            cands.sort_by_key(|x| self.cfg.blocks[*x].start);
986            if let Some(f) = cands.first() {
987                natural.insert(*f);
988            }
989        }
990        let natural = &natural;
991        if crate::dbg_flag!("JCDC_DBG_SESE") {
992            let mut nv: Vec<usize> = natural.iter().copied().collect();
993            nv.sort_unstable();
994            eprintln!("MXNAT header={} exits={:?} natural={:?}", header, exits, nv);
995        }
996        // Baseline conversion of the un-spliced body: the trial
997        // pathology scan compares against it (a splice that introduces a
998        // NEW Stmt::Labeled changed classify_loop's rotation — the
999        // ObjectInputStream readSerialData corruption route: the L2
1000        // re-rotation moved catch blocks across LVT ranges and varalloc
1001        // rebound the catch identity, markException(ex=null)).
1002        let base_labeled = {
1003            let mut conv = crate::convert::Converter::new(self.cfg, self.results)
1004                .with_copied_tails(self.copied_tails.clone())
1005                .with_final_fields(self.final_fields.clone());
1006            let base_stmt = conv.convert(body.clone());
1007            Self::count_labeled(&base_stmt)
1008        };
1009        let skip_e: Option<usize> = crate::dbg_value!("JCDC_MATEXIT_SKIP", usize);
1010        for &e in exits {
1011            if Some(e) == skip_e {
1012                continue;
1013            }
1014            if members.contains(&e)
1015                || natural.contains(&e)
1016                || ctx_loop_stack.contains(&e)
1017                || ctx_loop_headers.contains(&e)
1018                || self.is_terminator_block(e)
1019                || !self.exit_has_content(e, &barriers)
1020            {
1021                continue;
1022            }
1023            let mut cstop = barriers.clone();
1024            cstop.remove(&e);
1025            cstop.extend(natural.iter().copied());
1026            // The NATURAL CONTINUATION'S forward closure is parent-
1027            // rendered territory: a cascade may not swallow it (ois
1028            // readSerialData e=45's tu dragged in the post-loop blocks
1029            // 50-57 — double-rendered flow re-bound catch identities
1030            // downstream, markException(ex=null)). Barriers for the
1031            // closure: exits, enclosing headers, members — the closure
1032            // must not wrap around the loop.
1033            {
1034                let mut cl_bar: HashSet<usize> = exits.iter().copied().collect();
1035                cl_bar.extend(members.iter().copied());
1036                cl_bar.extend(ctx_loop_stack.iter().copied());
1037                cl_bar.extend(ctx_loop_headers.iter().copied());
1038                let mut cl: HashSet<usize> = HashSet::new();
1039                let mut cq: Vec<usize> = natural.iter().copied().collect();
1040                while let Some(b) = cq.pop() {
1041                    if !cl.insert(b) {
1042                        continue;
1043                    }
1044                    for &n2 in &self.cfg.blocks[b].succ {
1045                        if !cl_bar.contains(&n2) {
1046                            cq.push(n2);
1047                        }
1048                    }
1049                }
1050                cstop.extend(cl.iter().copied());
1051                cstop.remove(&e);
1052            }
1053            // ORPHANED exits only: E must NOT be forward-reachable from
1054            // any natural exit's flow. When it is, the parent renders that
1055            // flow after the Loop (ordinary continuation or finally-copy
1056            // machinery) and the break lands into it — splicing a copy
1057            // duplicated ObjectInputStream.readSerialData's finally-cleanup
1058            // retry (3x catch(ThreadDeath ignored), 已在方法中定义了变量)
1059            // and stranded ConcurrentHashMap.getTable tails (无法访问的语句).
1060            // An orphaned E is rendered NOWHERE else: cleanQueue's CAS
1061            // unlink phase (reachable only via member jumps, its flow
1062            // restarts the outer loop) and TempFileHelper's
1063            // `hasPermissions = true` block (flow joins the post-loop
1064            // continuation only THROUGH the shared break stub).
1065            // Barriered forward reachability: the RAW can_reach wraps
1066            // around the loop through the restart backedge and makes
1067            // every exit "reachable" from every natural (cleanQueue 6→
1068            // 24→0→1→…→11) — barriers are the other exits, members and
1069            // enclosing headers.
1070            let orphaned = !natural.contains(&e) && {
1071                let mut seen_o: HashSet<usize> = HashSet::new();
1072                let mut qo: Vec<usize> = natural.iter().copied().collect();
1073                for x in &qo {
1074                    seen_o.insert(*x);
1075                }
1076                let mut found = false;
1077                while let Some(b) = qo.pop() {
1078                    for &n2 in &self.cfg.blocks[b].succ {
1079                        if n2 == e {
1080                            found = true;
1081                            break;
1082                        }
1083                        if barriers.contains(&n2) || n2 == header {
1084                            continue;
1085                        }
1086                        if seen_o.insert(n2) {
1087                            qo.push(n2);
1088                        }
1089                    }
1090                    if found {
1091                        break;
1092                    }
1093                }
1094                !found
1095            };
1096            if !orphaned {
1097                continue;
1098            }
1099            // SHARED-PRED gate: an exit that shares a normal-flow
1100            // predecessor with a loop member is a SIBLING CONTINUATION
1101            // branch (b24 flows to BOTH the member b25 and the exit 45 —
1102            // ois readSerialData's finally-cleanup fork), not an orphaned
1103            // break target: the outer region renders that territory once,
1104            // and copying it double-renders the flow (catch identities
1105            // rebound downstream: markException(ex=null)). cleanQueue's
1106            // CAS exit (preds all members), Bits' epilogue (pred = its
1107            // own test) and tfh/FJP/CHM restorations have no shared pred.
1108            let shared_pred = self.cfg.blocks[e].pred.iter().any(|&p| {
1109                p != header
1110                    && !members.contains(&p)
1111                    && self.cfg.blocks[p].succ.len() >= 2
1112            });
1113            if shared_pred {
1114                continue;
1115            }
1116            // UPDATE-BLOCK gate: an exit whose forward flow (barred by the
1117            // other exits, members, enclosing headers and the natural
1118            // continuation) reaches THIS loop's header is the iteration
1119            // update block — copying it into a jump arm inverts break into
1120            // advance+continue (Pattern split's `break` became `++x2;
1121            // offset += len; continue L3`). A genuine break exit receives
1122            // member edges too (the cond's exit side) but never flows back
1123            // to the header inside the barriers (cleanQueue's CAS exit
1124            // restarts via the OUTER stub, TempFileHelper's joins the
1125            // post-loop continuation).
1126            let is_update = {
1127                let mut bar: HashSet<usize> = barriers.clone();
1128                bar.extend(natural.iter().copied());
1129                bar.remove(&e);
1130                let mut seen2: HashSet<usize> = HashSet::new();
1131                let mut q2: Vec<usize> = vec![e];
1132                seen2.insert(e);
1133                let mut found = false;
1134                while let Some(b) = q2.pop() {
1135                    if self.cfg.blocks[b].succ.iter().any(|&n| n == header) {
1136                        found = true;
1137                        break;
1138                    }
1139                    for &n in &self.cfg.blocks[b].succ {
1140                        if !bar.contains(&n) && seen2.insert(n) {
1141                            q2.push(n);
1142                        }
1143                    }
1144                }
1145                found
1146            };
1147            if is_update {
1148                continue;
1149            }
1150            // GROUP-TERRITORY gate: an exit whose flow lies inside any try
1151            // group's protected span (or is a handler) belongs to the
1152            // finally/catch machinery — copying it competes with the
1153            // dedupe/catch-identity passes (ObjectInputStream
1154            // .readSerialData: the e=4 micro-cascade copy re-bound the
1155            // catch vars — `catch (ClassNotFoundException ignored)` +
1156            // `ex = null` made markException receive NULL instead of the
1157            // exception, and the triple `ignored` collided). cleanQueue
1158            // and TempFileHelper have no groups over their cascades.
1159            let in_group = self
1160                .groups
1161                .iter()
1162                .any(|g| self.cfg.blocks[e].start >= g.start && self.cfg.blocks[e].start < g.end)
1163                || self.body_group.contains_key(&e)
1164                || self.handler_group.contains_key(&e);
1165            if in_group {
1166                continue;
1167            }
1168            let tu = crate::structure::reachable_within(self.cfg, e, &cstop);
1169            if !tu.contains(&e) || tu.len() > 48 {
1170                continue;
1171            }
1172            // HANDLER/THROW-TERRITORY gate: a cascade whose blocks
1173            // include an exception-handler head or a throw site is
1174            // finally/pending-rethrow machinery — copying it re-renders
1175            // catch flow and the downstream naming passes rebind the
1176            // catch identity (ois readSerialData e=24: the retry cascade
1177            // copy turned `catch (ClassNotFoundException e1) {
1178            // markException(passHandle, e1) }` into `catch (... ignored)
1179            // { ex = null; markException(passHandle, ex) }` — NULL
1180            // instead of the exception — plus a triple-`ignored`
1181            // redefinition error; conversion-time trial checks cannot
1182            // see it: the corruption happens in the post-convert
1183            // varalloc/naming passes). Such exits keep the bare break.
1184            // The e=4 single-Basic cascade (a genuine dropped
1185            // `hasSpecialReadMethod = true` restoration, golden-verified)
1186            // has neither handler heads nor throws and stays.
1187            if tu.iter().any(|&b| {
1188                self.handler_group.contains_key(&b)
1189                    || matches!(self.results[b].term, Term::Throw(_))
1190            }) {
1191                continue;
1192            }
1193            let mut fresh: HashSet<usize> = HashSet::new();
1194            // The walk's stop carries the OUTER barriers too (not just
1195            // cstop): Goto regions targeting them resolve to
1196            // break/continue at conversion instead of materializing
1197            // foreign territory as RawGoto copies (the ois finally-cleanup
1198            // duplication route).
1199            let mut wstop: HashSet<usize> = cstop.clone();
1200            wstop.extend(barriers.iter().copied());
1201            wstop.extend(natural.iter().copied());
1202            wstop.remove(&e);
1203            // copied_tails isolation: the cascade walk's internal
1204            // copy_walks register shared tails; leaking those into the
1205            // method's set makes the PARENT's own gotos to them elide as
1206            // reaches_copy_tail fallthrough — UnixPath.normalize lost its
1207            // whole `while (i1 < count) ... return new UnixPath(..)`
1208            // continuation (缺少返回语句 x2 trees) after an ACCEPTED splice.
1209            // Restore unconditionally: the cascade's registrations are
1210            // arm-local; the arm-internal trailing gotos still elide via
1211            // goto_is_last inside the copy.
1212            let saved_tails = self.copied_tails.clone();
1213            let r = self.walk(e, &tu, &wstop, &ctx_top_groups, &mut fresh, true);
1214            self.copied_tails = saved_tails;
1215            if matches!(r, Region::Empty) {
1216                continue;
1217            }
1218            // SELF-CONTAINED-FLOW gate: every successor of every cascade
1219            // block must stay inside the cascade universe or land on a
1220            // declared barrier (another exit, an enclosing header, the
1221            // natural continuation). A flow escaping elsewhere (into a
1222            // handler region or foreign territory) gets mangled by the
1223            // copy's conversion context (ObjectInputStream readSerialData:
1224            // the finally-cleanup retry copied as bare blocks re-bound
1225            // catch identities — markException received a null `ex`).
1226            let self_contained = tu.iter().all(|&b| {
1227                self.cfg.blocks[b].succ.iter().all(|&n| {
1228                    tu.contains(&n)
1229                        || cstop.contains(&n)
1230                        || exits.contains(&n)
1231                        || natural.contains(&n)
1232                        || ctx_loop_stack.contains(&n)
1233                        || ctx_loop_headers.contains(&n)
1234                })
1235            });
1236            if !self_contained {
1237                continue;
1238            }
1239            // A cascade containing a Try cannot be spliced as a copy: the
1240            // duplicated catch parameter collides with the original's
1241            // scope (jdk11/17 ObjectInputStream.readSerialData 已在方法中
1242            // 定义了变量 ignored — an inner catch may not redeclare an
1243            // outer catch's variable). Renaming catch vars is a
1244            // Stmt-level concern the region copy cannot express; those
1245            // exits keep the bare break (pre-fix behavior).
1246            if Self::region_contains_try(&r) {
1247                continue;
1248            }
1249            if crate::dbg_flag!("JCDC_DBG_SESE") {
1250                let txt = format!("{:?}", r);
1251                eprintln!(
1252                    "MATEXIT e={} tu={} r={}",
1253                    e,
1254                    tu.len(),
1255                    &txt[..txt.len().min(300)]
1256                );
1257            }
1258            // REDUNDANT BACKEDGE TRIM: a trailing Goto to THIS loop's own
1259            // header is the fall-out-equivalent (the parent renders
1260            // `continue` right after the loop anyway) — kept in the copy
1261            // it makes the arm all-abrupt and strands that continue
1262            // (ForkJoinPool awaitDone 无法访问的语句). Drop it; the arm
1263            // then completes normally into the parent's backedge.
1264            fn trim_backedge(r: &mut Region, header: usize) {
1265                if let Region::Seq(v) = r {
1266                    if matches!(v.last(), Some(Region::Goto { target }) if *target == header) {
1267                        v.pop();
1268                    }
1269                    return;
1270                }
1271                if matches!(r, Region::Goto { target } if *target == header) {
1272                    *r = Region::Empty;
1273                }
1274            }
1275            let mut r = r;
1276            trim_backedge(&mut r, header);
1277            if matches!(r, Region::Empty) {
1278                continue;
1279            }
1280            // CONVERSION-AWARE VALIDATION (the gate the region-level
1281            // predicates could not express — two experiment rounds proved
1282            // the harm classes arise at conversion/Stmt level): trial-
1283            // splice into a CLONE, convert it in a throwaway Converter,
1284            // and scan the Stmt tree for splice pathologies:
1285            // (1) abrupt-stranding — a terminating statement followed by
1286            //     siblings in the same block (the spliced cascade's inner
1287            //     gotos resolved to break/continue at this site where the
1288            //     ORIGINAL Goto{E} elided as fallthrough; jdk26
1289            //     ConcurrentHashMap.getTable 无法访问的语句). prune_
1290            //     unreachable would silently DELETE the stranded siblings.
1291            // (2) duplicate catch exception types — the copy re-rendered
1292            //     handler flow whose catch identity the naming passes
1293            //     then rebind (jdk11 ObjectInputStream.readSerialData
1294            //     markException(ex=null) semantic corruption).
1295            // Clean → commit the splice; pathology → roll back this exit
1296            // (the clone is dropped; nothing outside was mutated).
1297            // Two-tier site policy validated by trial conversion: tier 1
1298            // (loose) splices arm/tail Goto sites even under enclosing
1299            // Seqs with live siblings (CHM putVal's addCount restoration
1300            // lives on such an arm); tier 2 (strict) restricts to the
1301            // true flow-end chain (Bits phase-1's catch-handler Goto{16}
1302            // is followed by the handler's Goto{7} retry — splicing the
1303            // terminating epilogue there strands it). Validate tier 1;
1304            // on pathology fall back to tier 2 and re-validate; reject
1305            // the exit only when both fail.
1306            //
1307            // A cascade whose every flow path already terminates
1308            // (return/throw inlined via term_copy) takes NO trailing
1309            // Goto{e} (the break would render after a return — dead);
1310            // non-terminating cascades keep it so the break binding
1311            // survives. In-loop normalization: when the loop's natural
1312            // continuation is itself a barrier (backedge stub / outer
1313            // header / exit), re-append Goto{e} so the fall-out arm
1314            // re-iterates explicitly like the golden `continue`.
1315            let mut replacement = if self.region_all_paths_terminate(&r) {
1316                r.clone()
1317            } else {
1318                Region::Seq(vec![r.clone(), Region::Goto { target: e }])
1319            };
1320            let natural_is_barrier = natural
1321                .iter()
1322                .all(|&n0| exits.contains(&n0) || ctx_loop_stack.contains(&n0));
1323            if natural_is_barrier
1324                && self.region_all_paths_terminate(&r)
1325                && !matches!(replacement, Region::Seq(_))
1326            {
1327                replacement =
1328                    Region::Seq(vec![replacement, Region::Goto { target: e }]);
1329            }
1330            let convert_trial = |tb: Region| -> (Stmt, usize) {
1331                let mut conv = crate::convert::Converter::new(self.cfg, self.results)
1332                    .with_copied_tails(self.copied_tails.clone())
1333                    .with_final_fields(self.final_fields.clone());
1334                let st = conv.convert(tb);
1335                let lab = Self::count_labeled(&st);
1336                (st, lab)
1337            };
1338            let trial1 = {
1339                let mut tb = body.clone();
1340                Self::splice_exit_copies_with(&mut tb, e, &replacement, false);
1341                self.replace_empty_arms_for(&mut tb, e, &replacement);
1342                tb
1343            };
1344            let (stmt1, lab1) = convert_trial(trial1);
1345            let strict = if !Self::stmt_splice_pathology(&stmt1) && lab1 == base_labeled {
1346                false
1347            } else {
1348                let trial2 = {
1349                    let mut tb = body.clone();
1350                    Self::splice_exit_copies_with(&mut tb, e, &replacement, true);
1351                    self.replace_empty_arms_for(&mut tb, e, &replacement);
1352                    tb
1353                };
1354                let (stmt2, lab2) = convert_trial(trial2);
1355                if Self::stmt_splice_pathology(&stmt2) || lab2 != base_labeled {
1356                    if crate::dbg_flag!("JCDC_DBG_SESE") {
1357                        eprintln!("MATEXIT-REJECT e={} both tiers pathological", e);
1358                    }
1359                    continue;
1360                }
1361                true
1362            };
1363            if crate::dbg_flag!("JCDC_DBG_SESE") {
1364                let mut tusize: Vec<usize> = tu.iter().copied().collect();
1365                tusize.sort_unstable();
1366                eprintln!("MATEXIT-ACCEPT e={} strict={} tu={:?}", e, strict, tusize);
1367                for (gi, g) in self.groups.iter().enumerate() {
1368                    let overlap: Vec<usize> = tusize.iter().copied().filter(|&b| {
1369                        self.cfg.blocks[b].start >= g.start && self.cfg.blocks[b].start < g.end
1370                    }).collect();
1371                    if !overlap.is_empty() {
1372                        eprintln!("MXGROUP gi={} span=({},{}) overlap={:?}", gi, g.start, g.end, overlap);
1373                    }
1374                }
1375                let bg: Vec<usize> = tusize.iter().copied().filter(|b| self.body_group.contains_key(b)).collect();
1376                let hg: Vec<usize> = tusize.iter().copied().filter(|b| self.handler_group.contains_key(b)).collect();
1377                eprintln!("MXGH body_group={:?} handler_group={:?}", bg, hg);
1378            }
1379            let h1 = Self::splice_exit_copies_with(body, e, &replacement, strict);
1380            let h2 = self.replace_empty_arms_for(body, e, &replacement);
1381            if h1 || h2 {
1382                MATEXIT_FIRED.with(|f| f.set(true));
1383            }
1384        }
1385    }
1386
1387    /// True when every flow path through the region ends at a
1388    /// return/throw (Basic/CopyStmts of a terminator block, or a Goto
1389    /// whose target term-copies inline). Empty arms fall through, so an
1390    /// If only terminates when BOTH arms do; a Seq when its last element
1391    /// does.
1392    fn region_all_paths_terminate(&self, r: &Region) -> bool {
1393        match r {
1394            Region::Basic { block } | Region::CopyStmts { block } => {
1395                self.is_terminator_block(*block)
1396            }
1397            Region::Goto { target } => self.is_terminator_block(*target),
1398            Region::Seq(v) => v
1399                .last()
1400                .map(|x| self.region_all_paths_terminate(x))
1401                .unwrap_or(false),
1402            Region::If { then_r, else_r, .. } => {
1403                self.region_all_paths_terminate(then_r)
1404                    && self.region_all_paths_terminate(else_r)
1405            }
1406            Region::Try { body, catches, .. } => {
1407                self.region_all_paths_terminate(body)
1408                    && catches
1409                        .iter()
1410                        .all(|(_, _, h)| self.region_all_paths_terminate(h))
1411            }
1412            _ => false,
1413        }
1414    }
1415
1416    /// Count Stmt::Labeled nodes in a converted tree (rotation-shape
1417    /// fingerprint for the trial comparison).
1418    fn count_labeled(s: &Stmt) -> usize {
1419        let mut n = match s {
1420            Stmt::Labeled { .. } => 1,
1421            _ => 0,
1422        };
1423        match s {
1424            Stmt::Block(v) => {
1425                for x in v {
1426                    n += Self::count_labeled(x);
1427                }
1428            }
1429            Stmt::If { then_stmt, else_stmt, .. } => {
1430                n += Self::count_labeled(then_stmt);
1431                if let Some(e) = else_stmt {
1432                    n += Self::count_labeled(e);
1433                }
1434            }
1435            Stmt::While { body, .. }
1436            | Stmt::DoWhile { body, .. }
1437            | Stmt::ForEach { body, .. }
1438            | Stmt::Labeled { body, .. }
1439            | Stmt::Synchronized { body, .. } => n += Self::count_labeled(body),
1440            Stmt::For { init, body, .. } => {
1441                for x in init {
1442                    n += Self::count_labeled(x);
1443                }
1444                n += Self::count_labeled(body);
1445            }
1446            Stmt::Try { body, catches, finally, .. } => {
1447                n += Self::count_labeled(body);
1448                for c in catches {
1449                    n += Self::count_labeled(&c.body);
1450                }
1451                if let Some(f) = finally {
1452                    n += Self::count_labeled(f);
1453                }
1454            }
1455            Stmt::Switch { cases, default, .. } => {
1456                for c in cases {
1457                    for x in &c.body {
1458                        n += Self::count_labeled(x);
1459                    }
1460                }
1461                if let Some(d) = default {
1462                    n += Self::count_labeled(d);
1463                }
1464            }
1465            _ => {}
1466        }
1467        n
1468    }
1469
1470    /// Scan a converted Stmt tree for splice pathologies (see
1471    /// materialize_content_exits). Conservative: any hit rolls the exit
1472    /// back to the bare-break shape.
1473    pub fn stmt_splice_pathology(s: &Stmt) -> bool {
1474        Self::pathology_in(s, None)
1475    }
1476
1477    /// Label-aware JLS-14.21 termination for the pathology scanner:
1478    /// `break L1` targeting an OUTER labeled loop escapes an inner
1479    /// `while (true)` WITHOUT letting it complete normally — only
1480    /// Break(None) or a break carrying THIS loop's own label does.
1481    /// stmt_terminates's any-labeled-break conservatism (correct for
1482    /// prune_unreachable's deletion side) false-negatived the
1483    /// ForwardingNode.find strand (无法访问的语句 x3 trees: the spliced
1484    /// cascade made the inner while(true) body all-abrupt, the outer
1485    /// break-through left the loop non-completing, and the parent's next
1486    /// sibling was unreachable).
1487    fn terminates_scoped(s: &Stmt, own: Option<&str>) -> bool {
1488        fn is_true_expr(c: &crate::ir::expr::Expr) -> bool {
1489            matches!(c, crate::ir::expr::Expr::Const(crate::ir::expr::ConstVal::Int(1)))
1490        }
1491        fn deep_own_break(s: &Stmt, own: Option<&str>) -> bool {
1492            match s {
1493                Stmt::Break(None) => true,
1494                Stmt::Break(Some(l)) => Some(l.as_str()) == own,
1495                Stmt::Block(v) => v.iter().any(|x| deep_own_break(x, own)),
1496                Stmt::If { then_stmt, else_stmt, .. } => {
1497                    deep_own_break(then_stmt, own)
1498                        || else_stmt
1499                            .as_ref()
1500                            .map(|e| deep_own_break(e, own))
1501                            .unwrap_or(false)
1502                }
1503                Stmt::While { body, .. }
1504                | Stmt::DoWhile { body, .. }
1505                | Stmt::ForEach { body, .. }
1506                | Stmt::For { body, .. }
1507                | Stmt::Synchronized { body, .. }
1508                | Stmt::Labeled { body, .. } => deep_own_break(body, own),
1509                Stmt::Switch { cases, default, .. } => {
1510                    cases
1511                        .iter()
1512                        .any(|c| c.body.iter().any(|x| deep_own_break(x, own)))
1513                        || default
1514                            .as_ref()
1515                            .map(|d| deep_own_break(d, own))
1516                            .unwrap_or(false)
1517                }
1518                Stmt::Try { body, catches, finally, .. } => {
1519                    deep_own_break(body, own)
1520                        || catches.iter().any(|c| deep_own_break(&c.body, own))
1521                        || finally
1522                            .as_ref()
1523                            .map(|f| deep_own_break(f, own))
1524                            .unwrap_or(false)
1525                }
1526                Stmt::TryWithResources { resources, body, catches, finally, .. } => {
1527                    resources.iter().any(|r| deep_own_break(r, own))
1528                        || deep_own_break(body, own)
1529                        || catches.iter().any(|c| deep_own_break(&c.body, own))
1530                        || finally
1531                            .as_ref()
1532                            .map(|f| deep_own_break(f, own))
1533                            .unwrap_or(false)
1534                }
1535                _ => false,
1536            }
1537        }
1538        match s {
1539            Stmt::Return(_) | Stmt::Throw(_) | Stmt::Break(_) | Stmt::Continue(_) => true,
1540            Stmt::Block(v) => v
1541                .last()
1542                .map(|x| Structurer::terminates_scoped(x, own))
1543                .unwrap_or(false),
1544            Stmt::If { then_stmt, else_stmt: Some(e), .. } => {
1545                Structurer::terminates_scoped(then_stmt, own)
1546                    && Structurer::terminates_scoped(e, own)
1547            }
1548            Stmt::Synchronized { body, .. } => Structurer::terminates_scoped(body, own),
1549            Stmt::Labeled { label, body } => Structurer::terminates_scoped(body, Some(label)),
1550            Stmt::While { cond, body } | Stmt::DoWhile { body, cond }
1551                if is_true_expr(cond) && !deep_own_break(body, own) =>
1552            {
1553                true
1554            }
1555            Stmt::For { cond, body, .. }
1556                if match cond {
1557                    None => true,
1558                    Some(c) => is_true_expr(c),
1559                } && !deep_own_break(body, own) =>
1560            {
1561                true
1562            }
1563            Stmt::Try { body, catches, .. } => {
1564                Structurer::terminates_scoped(body, own)
1565                    && catches
1566                        .iter()
1567                        .all(|c| Structurer::terminates_scoped(&c.body, own))
1568            }
1569            _ => false,
1570        }
1571    }
1572
1573    fn pathology_in(s: &Stmt, own: Option<&str>) -> bool {
1574        fn stmt_is_empty(x: &Stmt) -> bool {
1575            match x {
1576                Stmt::Block(v) => v.iter().all(stmt_is_empty),
1577                Stmt::Comment(_) => true,
1578                _ => false,
1579            }
1580        }
1581        fn dup_catch(catches: &[crate::ir::stmt::Catch]) -> bool {
1582            for i in 0..catches.len() {
1583                for j in i + 1..catches.len() {
1584                    if !catches[i].exc.is_empty() && catches[i].exc == catches[j].exc {
1585                        return true;
1586                    }
1587                }
1588            }
1589            false
1590        }
1591        match s {
1592            Stmt::Block(v) => {
1593                if v.len() > 1 {
1594                    if let Some(pos) = v
1595                        .iter()
1596                        .position(|x| Structurer::terminates_scoped(x, own))
1597                    {
1598                        // EMPTY trailing blocks are conversion placeholders
1599                        // (elided gotos) that cleanup() removes before
1600                        // render — javac never sees them. Only NON-EMPTY
1601                        // stranded siblings are pathologies (the trial
1602                        // scans raw converter output; Bits phase-1's
1603                        // [terminating Try, Block([])] false positive
1604                        // vetoed the splice).
1605                        //
1606                        // EXCEPT when the terminator is an emit-level
1607                        // dead-end while(true): Printer::into_string runs
1608                        // truncate_dead_ends over the FINAL tree and
1609                        // strips everything after it, so javac never sees
1610                        // those siblings either — flagging them vetoed the
1611                        // matexit shape whose break-cascade arms carry the
1612                        // loop's real exits (jdk26 ConcurrentLinkedQueue
1613                        // .poll: the restart Continue(None) after the
1614                        // dead-end inner traversal forced the retry, whose
1615                        // matexit-OFF shape drops the cas-success p==h
1616                        // `return item` exit — the polled item was
1617                        // dequeued but never returned).
1618                        let scan_len =
1619                            if crate::analysis::dead_end_infinite_while(&v[pos]) {
1620                                pos + 1
1621                            } else {
1622                                v.len()
1623                            };
1624                        if v[pos + 1..scan_len].iter().any(|x| !stmt_is_empty(x)) {
1625                            return true;
1626                        }
1627                        // Truncated siblings are invisible to javac — their
1628                        // interiors cannot be pathologies either.
1629                        return v[..scan_len]
1630                            .iter()
1631                            .any(|x| Structurer::pathology_in(x, own));
1632                    }
1633                }
1634                v.iter().any(|x| Structurer::pathology_in(x, own))
1635            }
1636            Stmt::If { then_stmt, else_stmt, .. } => {
1637                Structurer::pathology_in(then_stmt, own)
1638                    || else_stmt
1639                        .as_ref()
1640                        .map(|e| Structurer::pathology_in(e, own))
1641                        .unwrap_or(false)
1642            }
1643            // Loop bodies bind unlabeled breaks to themselves: the
1644            // enclosing label does NOT propagate in (an inner while(true)
1645            // under `L1:` is still non-completing when its body breaks
1646            // to L1 — the break escapes THROUGH it). Synchronized is
1647            // transparent.
1648            Stmt::While { body, .. }
1649            | Stmt::DoWhile { body, .. }
1650            | Stmt::ForEach { body, .. } => Structurer::pathology_in(body, None),
1651            Stmt::Synchronized { body, .. } => Structurer::pathology_in(body, own),
1652            Stmt::For { init, body, .. } => {
1653                init.iter().any(|x| Structurer::pathology_in(x, own))
1654                    || Structurer::pathology_in(body, None)
1655            }
1656            Stmt::Labeled { label, body } => Structurer::pathology_in(body, Some(label)),
1657            Stmt::Try { body, catches, finally, .. } => {
1658                dup_catch(catches)
1659                    || Structurer::pathology_in(body, own)
1660                    || catches
1661                        .iter()
1662                        .any(|c| Structurer::pathology_in(&c.body, own))
1663                    || finally
1664                        .as_ref()
1665                        .map(|f| Structurer::pathology_in(f, own))
1666                        .unwrap_or(false)
1667            }
1668            Stmt::TryWithResources { body, catches, finally, .. } => {
1669                dup_catch(catches)
1670                    || Structurer::pathology_in(body, own)
1671                    || catches
1672                        .iter()
1673                        .any(|c| Structurer::pathology_in(&c.body, own))
1674                    || finally
1675                        .as_ref()
1676                        .map(|f| Structurer::pathology_in(f, own))
1677                        .unwrap_or(false)
1678            }
1679            Stmt::Switch { cases, default, .. } => {
1680                cases
1681                    .iter()
1682                    .any(|c| c.body.iter().any(|x| Structurer::pathology_in(x, own)))
1683                    || default
1684                        .as_ref()
1685                        .map(|d| Structurer::pathology_in(d, own))
1686                        .unwrap_or(false)
1687            }
1688            _ => false,
1689        }
1690    }
1691
1692    /// True when the region tree contains any Try.
1693    fn region_contains_try(r: &Region) -> bool {
1694        match r {
1695            Region::Try { .. } => true,
1696            Region::Seq(v) => v.iter().any(Self::region_contains_try),
1697            Region::If { then_r, else_r, .. } => {
1698                Self::region_contains_try(then_r) || Self::region_contains_try(else_r)
1699            }
1700            Region::Loop { body, .. } => Self::region_contains_try(body),
1701            Region::Switch { cases, default, .. } => {
1702                cases.iter().any(|(_, cr)| Self::region_contains_try(cr))
1703                    || default
1704                        .as_ref()
1705                        .map(|d| Self::region_contains_try(d))
1706                        .unwrap_or(false)
1707            }
1708            _ => false,
1709        }
1710    }
1711
1712    /// Replace EMPTY If arms whose branch target is `e` with the
1713    /// replacement region: the Bits phase-1 shape has NO Goto{e} site at
1714    /// all — the COND's fall==follow==exit renders the success arm as
1715    /// Region::Empty and the body falls out to the backedge (re-loop).
1716    /// The empty arm IS the jump to e; materializing it restores the
1717    /// golden `if (!call) continue; else { epilogue; }`.
1718    fn replace_empty_arms_for(&self, r: &mut Region, e: usize, replacement: &Region) -> bool {
1719        match r {
1720            Region::If { block, then_r, else_r, .. } => {
1721                let succs = &self.cfg.blocks[*block].succ;
1722                let mut hit = false;
1723                if matches!(**then_r, Region::Empty) && succs.get(1) == Some(&e) {
1724                    *then_r = Box::new(replacement.clone());
1725                    hit = true;
1726                }
1727                if matches!(**else_r, Region::Empty) && succs.first() == Some(&e) {
1728                    *else_r = Box::new(replacement.clone());
1729                    hit = true;
1730                }
1731                hit |= self.replace_empty_arms_for(then_r, e, replacement);
1732                hit |= self.replace_empty_arms_for(else_r, e, replacement);
1733                hit
1734            }
1735            Region::Seq(v) => v
1736                .iter_mut()
1737                .fold(false, |h, x| h | self.replace_empty_arms_for(x, e, replacement)),
1738            Region::Try { body, catches, .. } => {
1739                let mut hit = self.replace_empty_arms_for(body, e, replacement);
1740                for (_, _, h) in catches.iter_mut() {
1741                    hit |= self.replace_empty_arms_for(h, e, replacement);
1742                }
1743                hit
1744            }
1745            Region::Loop { body, .. } => self.replace_empty_arms_for(body, e, replacement),
1746            Region::Switch { cases, default, .. } => {
1747                let mut hit = false;
1748                for (_, cr) in cases.iter_mut() {
1749                    hit |= self.replace_empty_arms_for(cr, e, replacement);
1750                }
1751                if let Some(d) = default {
1752                    hit |= self.replace_empty_arms_for(d, e, replacement);
1753                }
1754                hit
1755            }
1756            _ => false,
1757        }
1758    }
1759
1760    /// Replace `Goto{e}` sites in the region tree with `replacement`
1761    /// (per-arrival copies; Region: Clone). `strict`: only sites on the
1762    /// true flow-end chain (every enclosing Seq position is last) are
1763    /// replaced; loose mode additionally replaces arm-internal sites
1764    /// whose enclosing Seq has live siblings. A MID-Seq Goto leaf is
1765    /// never replaced in either mode (live siblings follow it directly).
1766    fn splice_exit_copies_with(
1767        r: &mut Region,
1768        e: usize,
1769        replacement: &Region,
1770        strict: bool,
1771    ) -> bool {
1772        match r {
1773            Region::Goto { target } if *target == e => {
1774                *r = replacement.clone();
1775                true
1776            }
1777            Region::Seq(v) => {
1778                let mut hit = false;
1779                let n = v.len();
1780                for (i, x) in v.iter_mut().enumerate() {
1781                    let last = i + 1 == n;
1782                    if matches!(x, Region::Goto { target } if *target == e) {
1783                        if last {
1784                            *x = replacement.clone();
1785                            hit = true;
1786                        }
1787                    } else {
1788                        hit |= Self::splice_exit_copies_with(
1789                            x,
1790                            e,
1791                            replacement,
1792                            strict || !last,
1793                        );
1794                    }
1795                }
1796                hit
1797            }
1798            Region::If { then_r, else_r, .. } => {
1799                let a = Self::splice_exit_copies_with(then_r, e, replacement, strict);
1800                let b = Self::splice_exit_copies_with(else_r, e, replacement, strict);
1801                a | b
1802            }
1803            Region::Try { body, catches, .. } => {
1804                let mut hit = Self::splice_exit_copies_with(body, e, replacement, strict);
1805                for (_, _, h) in catches.iter_mut() {
1806                    hit |= Self::splice_exit_copies_with(h, e, replacement, strict);
1807                }
1808                hit
1809            }
1810            Region::Loop { body, .. } => {
1811                Self::splice_exit_copies_with(body, e, replacement, strict)
1812            }
1813            Region::Switch { cases, default, .. } => {
1814                let mut hit = false;
1815                for (_, cr) in cases.iter_mut() {
1816                    hit |= Self::splice_exit_copies_with(cr, e, replacement, strict);
1817                }
1818                if let Some(d) = default {
1819                    hit |= Self::splice_exit_copies_with(d, e, replacement, strict);
1820                }
1821                hit
1822            }
1823            _ => false,
1824        }
1825    }
1826
1827    fn sese_region_with_scope(
1828        &mut self,
1829        entry: usize,
1830        stop: &HashSet<usize>,
1831        reach: HashSet<usize>,
1832        ctx: &mut SeseCtx,
1833    ) -> Region {
1834        ctx.depth += 1;
1835        let deep = ctx.depth > 400;
1836        let mut parts: Vec<Region> = Vec::new();
1837        let mut cur = entry;
1838        let mut guard = 0usize;
1839        // True when the walk advanced into `cur` through an explicit Goto
1840        // terminator (vs. natural fall-through): a jump into a stop must be
1841        // materialized as a Goto region (the converter resolves it to
1842        // break/continue, elides it as if-follow fallthrough, or inlines a
1843        // shared return/throw via term_copy) — silently ending the region
1844        // DROPS the jump (jdk17 String LATIN1 fast-path `goto 839` lost its
1845        // `return;` against the stop=shared-exit, double-assigning finals).
1846        let mut last_via_goto = false;
1847        loop {
1848            guard += 1;
1849            if crate::dbg_flag!("JCDC_DBG_GOTO") {
1850                eprintln!("SESETOP entry={} cur={} parts={} reach={} stop={} consumed={} last_via_goto={}",
1851                    entry, cur, parts.len(), reach.contains(&cur), stop.contains(&cur),
1852                    ctx.consumed.contains(&cur), last_via_goto);
1853            }
1854            if deep || guard > ctx.universe.len() * 4 + 64 {
1855                break;
1856            }
1857            // A try body reaches its tail through inlined/duplicated finally
1858            // copies, so reachable_within (normal succ only) can miss the
1859            // post-finally continuation even though it is a legitimate forward
1860            // tail (unconsumed, in-universe, not a stop). Structure it anyway;
1861            // monotonicity is preserved by the consumed check below (a block
1862            // owned by a sibling is consumed -> Goto, never re-structured).
1863            let forward_tail = !reach.contains(&cur)
1864                && ctx.universe.contains(&cur)
1865                && !ctx.consumed.contains(&cur)
1866                && !stop.contains(&cur);
1867            if !forward_tail
1868                && (stop.contains(&cur) || !ctx.universe.contains(&cur) || !reach.contains(&cur))
1869            {
1870                // A region whose ENTRY is itself a stop block was branched-to
1871                // deliberately (a cond branch / `break` to a loop exit, or a
1872                // jump to an enclosing follow). Emit Goto{entry} so the
1873                // converter resolves it to break/continue — or elides it as a
1874                // natural fallthrough when entry is the enclosing if-follow.
1875                // Without this, a `break outer` branch to a stop block renders
1876                // as an EMPTY branch and the loop exit is silently lost
1877                // (Legacy6 `if (a==2 && b==2) break outer;` -> infinite loop).
1878                if cur == entry && stop.contains(&entry) && parts.is_empty() {
1879                    parts.push(Region::Goto { target: entry });
1880                } else if last_via_goto && stop.contains(&cur) && !parts.is_empty() {
1881                    parts.push(Region::Goto { target: cur });
1882                }
1883                break;
1884            }
1885            if ctx.consumed.contains(&cur) {
1886                // Already structured (a shared merge owned by an earlier
1887                // sibling/region). A shared TERMINATOR (return/throw) is
1888                // safely duplicated inline at each arrival site (it has no
1889                // outgoing flow) — this is how `if (t || explode()) return 1;
1890                // return 2;` keeps the `return 1` on both the t-true and
1891                // explode-true paths. Anything else becomes a Goto (resolved
1892                // to break/continue/label at conversion), never a re-walk.
1893                if matches!(self.results[cur].term, Term::Return(_) | Term::Throw(_)) {
1894                    // Shared terminator: duplicate inline (no outgoing
1895                    // flow). Duplicating a final-field write is REQUIRED
1896                    // here: the paths are disjoint (each copy ends in the
1897                    // return), and eliding it would drop the assignment
1898                    // from this path entirely ("variable f might not have
1899                    // been assigned" — Fin/Long$LongCache fresh path).
1900                    parts.push(Region::CopyStmts { block: cur });
1901                } else if ctx.loop_stack.contains(&cur) {
1902                    // Back-edge to an enclosing loop header: a `continue`
1903                    // (resolved at conversion). NEVER copy_walk it — that would
1904                    // re-walk the whole loop as a nested duplicate. The Goto is
1905                    // emitted even when parts is empty: a branch region that
1906                    // IS the jump to the header (`if (c) continue;`) must not
1907                    // collapse to Empty — that silently dropped BOTH continues
1908                    // of StringUTF16.codePointCount, running the loop tail
1909                    // unconditionally.
1910                    parts.push(Region::Goto { target: cur });
1911                } else if let Some(&h) = ctx.loop_stack.iter().rev().find(|&&h| {
1912                    crate::structure::can_reach_cfg(self.cfg, cur, h, 4096)
1913                }) {
1914                    // The consumed block flows back into an enclosing loop
1915                    // (a catch's retry back-edge into the loop-top try,
1916                    // jdk11 ObjectStreamClass.getInheritableMethod): a
1917                    // copy_walk here unrolls the loop into nested duplicate
1918                    // tries (COPY_DEPTH-bounded, then wrong). Resolve as
1919                    // `continue` of the nearest such loop instead — walk's
1920                    // loops_stack guard equivalent (walk tracks its own
1921                    // stack; SESE keeps it in ctx).
1922                    parts.push(Region::Goto { target: h });
1923                } else if !stop.contains(&cur) {
1924                    // Shared non-terminator reached from a divergent sibling
1925                    // (e.g. the continuation after `if (A && B) return X;`):
1926                    // copy_walk re-structures it inline, COPY_DEPTH-bounded so
1927                    // nested shared chains stay monotone (no divergence). The
1928                    // copy is additionally barred from leaving THIS region's
1929                    // scope: reachable_within(copy) must not pick up blocks the
1930                    // enclosing walk could never consume (that is how the
1931                    // post-loop tail vanished from a copied loop branch).
1932                    // Falls back to a Goto when the cap is hit or it loops back.
1933                    let mut cstop: HashSet<usize> = stop.iter().copied().collect();
1934                    for &u in ctx.universe.iter() {
1935                        if !reach.contains(&u) {
1936                            cstop.insert(u);
1937                        }
1938                    }
1939                    let prev_allow = crate::structure::COPY_ALLOW_CONFLUENCE
1940                        .with(|c| c.replace(true));
1941                    let copied = self.copy_walk(cur, &cstop, &ctx.top_groups, usize::MAX);
1942                    crate::structure::COPY_ALLOW_CONFLUENCE
1943                        .with(|c| c.set(prev_allow));
1944                    match copied {
1945                        Some(r) => parts.push(r),
1946                        None => {
1947                            if !parts.is_empty() {
1948                                parts.push(Region::Goto { target: cur });
1949                            }
1950                        }
1951                    }
1952                } else if !stop.contains(&cur)
1953                    || !self.sese_stop_chain_to_claimed_terminator(cur, stop, ctx)
1954                {
1955                    if !parts.is_empty() {
1956                        parts.push(Region::Goto { target: cur });
1957                    }
1958                } else {
1959                    // A stop member whose forward flow is a single-succ chain
1960                    // into an ALREADY-CONSUMED terminator: copy the chain
1961                    // inline. The restart-loop switch (jdk26 DecimalFormat
1962                    // guarded typeSwitch): the guard-pass body is a loop exit
1963                    // that flows ONLY to the shared areturn every case region
1964                    // absorbed — `break L1` would land after the loop where
1965                    // nothing remains and the body is lost. Normal loop exits
1966                    // chain to UNCLAIMED continuations and keep the Goto
1967                    // (inlining those rewrote breaks into returns and
1968                    // degraded while-cond loops, ThreadPoolExecutor).
1969                    let mut cstop: HashSet<usize> = stop.iter().copied().collect();
1970                    for &u in ctx.universe.iter() {
1971                        if !reach.contains(&u) {
1972                            cstop.insert(u);
1973                        }
1974                    }
1975                    match self.copy_walk(cur, &cstop, &ctx.top_groups, usize::MAX) {
1976                        Some(r) => parts.push(r),
1977                        None => {
1978                            if !parts.is_empty() {
1979                                parts.push(Region::Goto { target: cur });
1980                            }
1981                        }
1982                    }
1983                }
1984                break;
1985            }
1986            // Value-diamond fold: when `cur` roots a pre-folded pure-value
1987            // diamond (computed by the builder), collapse it — consume the
1988            // absorbed branch blocks and continue at the merge, whose input
1989            // stack already holds the folded ternary. This is what turns
1990            // boolean short-circuits (`a && b`, `a || b`) and `c ? a : b`
1991            // into expressions instead of structuring them as control-flow
1992            // ifs with empty branches (the milestone-3 parity with walk).
1993            // A block that is ALSO the exact start of a try group must be
1994            // structured as the try first — walk checks groups before folds.
1995            // Collapsing it as a value diamond skips the try entirely and
1996            // orphans the handler (URL.createURLStreamHandler: stage-1 string
1997            // switch dispatch folded, try [5,86) never fired, bare reflective
1998            // call without its catch -> unreported checked exceptions; a jdk11/17
1999            // corpus blocker). The diamond is re-folded inside the try body walk.
2000            let fold_blocked_by_group = (0..self.groups.len()).any(|gi| {
2001                self.groups[gi].start == self.cfg.blocks[cur].start
2002                    && self.groups[gi].end >= self.cfg.blocks[cur].end
2003                    && !self.handler_group.contains_key(&cur)
2004            });
2005            if let Some(&merge) = self.fold_root_to_merge.get(&cur) {
2006                if !fold_blocked_by_group {
2007                if let Some((_root, vis)) = self.fold_regions.get(&merge) {
2008                    if !self.results[cur].stmts.is_empty() {
2009                        parts.push(Region::Basic { block: cur });
2010                    }
2011                    for &v in vis.iter() {
2012                        ctx.consumed.insert(v);
2013                    }
2014                    ctx.consumed.insert(cur);
2015                    cur = merge;
2016                    last_via_goto = false;
2017                    continue;
2018                }
2019                }
2020            }
2021            let b = &self.cfg.blocks[cur];
2022            if b.ins_len == 0 {
2023                ctx.consumed.insert(cur);
2024                match b.succ.first().copied() {
2025                    Some(n) => {
2026                        cur = n;
2027                        last_via_goto = false;
2028                        continue;
2029                    }
2030                    None => break,
2031                }
2032            }
2033
2034            // Loop header? Structure the natural loop, then continue at its
2035            // follow (the exit the loop post-dominates toward).
2036            // Try group starting exactly here? Reuse the tuned try builder
2037            // (body/handlers structured by walk over their sub-scopes; the
2038            // handler regions and finally duplication are intricate). Pick the
2039            // OUTERMOST group starting at cur (largest span); structure_try
2040            // handles nested groups internally.
2041            {
2042                let bs = self.cfg.blocks[cur].start;
2043                let be = self.cfg.blocks[cur].end;
2044                let group_here = (0..self.groups.len())
2045                    .filter(|&gi| {
2046                        self.groups[gi].start == bs
2047                            && self.groups[gi].end >= be
2048                            && !self.handler_group.contains_key(&cur)
2049                    })
2050                    .max_by_key(|&gi| self.groups[gi].end);
2051                // The group starts at a LOOP HEADER whose back edge lies
2052                // OUTSIDE the protected span (`for (;;) { try { ... }
2053                // catch { ... } ...retry... }` — jdk26
2054                // ClassValue.getFromHashMap): the loop is the enclosing
2055                // construct. Structuring the try first consumes the
2056                // header and the back edge gets duplicated (3× unrolled
2057                // copies, lost retry loop, missing return). Let the
2058                // loop-header branch below win; the body walk re-finds
2059                // this group at its start. try{while} keeps group-first:
2060                // its back edge is INSIDE the protected span.
2061                //
2062                // FINALLY-WRAP exception: `try { while (true) { ... }
2063                // finally { ... } }` (jdk26 KQueuePort
2064                // EventHandlerTask.run) — javac splits the protection
2065                // around the inlined finally copies, so the FIRST range
2066                // ends mid-loop and the back edges sit outside it: the
2067                // ClassValue span shape. What separates the two is the
2068                // handler's TAIL: ClassValue's catch continues the
2069                // retry (the loop must own the header), while a finally
2070                // handler ends in a pending rethrow and never returns
2071                // to the header. Fingerprint: an `any`-range of this
2072                // group's handler set whose end extends INTO the handler
2073                // block — the protection blankets the whole loop up to
2074                // the handler entry (KQueuePort range 171..211 over
2075                // handler 209). Loop-first there nests the try inside
2076                // the loop and walks the loop's break-path finally copy
2077                // (pollTask()==null fall-out) as the post-loop
2078                // continuation — a second threadExit after the
2079                // all-paths-return while(true): 无法访问的语句 x3 trees.
2080                let finally_wrap = |gi: usize| -> bool {
2081                    let g = &self.groups[gi];
2082                    self.cfg.exc_ranges.iter().any(|r| {
2083                        r.catch_type.is_none()
2084                            && g.handlers.iter().any(|(h2, t2)| t2.is_none() && *h2 == r.handler)
2085                            && r.start < r.handler
2086                            && self.cfg.block_at(r.handler).is_some_and(|hb| {
2087                                r.end > self.cfg.blocks[hb].start
2088                                    && r.end < self.cfg.blocks[hb].end.saturating_add(8)
2089                            })
2090                    })
2091                };
2092                // The finally-wrap fingerprint overrides BOTH loop-first
2093                // rules (back-edge-outside-span and exc-retry-header):
2094                // KQueuePort's loop is also an exc-retry loop (the
2095                // InterruptedException handler's `goto 16` continue), and
2096                // the wrap still belongs outside it — structure_try's
2097                // body walk re-structures the loop at the header via
2098                // sese_exc_retry_headers (the parseBest mechanism). The
2099                // typed-catch retry shapes (parseBest DateTimeException,
2100                // Process.waitFor) have no `any` handler and never trip
2101                // the fingerprint.
2102                let group_here = group_here.filter(|&gi| {
2103                    !ctx.loop_headers.contains(&cur)
2104                        || finally_wrap(gi)
2105                        || (!ctx.exc_retry_headers.contains(&cur)
2106                            && !self.cfg.blocks[cur].pred.iter().any(|&p| {
2107                                p != cur
2108                                    && ctx.idom.dominates(cur, p)
2109                                    && (self.cfg.blocks[p].end <= self.groups[gi].start
2110                                        || self.cfg.blocks[p].start >= self.groups[gi].end)
2111                            }))
2112                });
2113                if let Some(gi) = group_here {
2114                    let outer_universe = ctx.universe.clone();
2115                    let mut claimed = ctx.consumed.clone();
2116                    let try_region =
2117                        self.structure_try(gi, &ctx.universe, &outer_universe, &mut claimed, stop);
2118                    ctx.consumed = claimed;
2119                    parts.push(try_region);
2120                    let gend = self.groups[gi].end;
2121                    // structure_try absorbs a trailing terminator sitting at
2122                    // exactly g.end into the body (the protected-range areturn
2123                    // javac excludes; ALL its preds are body blocks). It is
2124                    // now claimed and lives INSIDE the try; letting the
2125                    // continuation search below pick it (it passes
2126                    // start >= gend + consumed + is_term) re-emits the body's
2127                    // own return after the try (ObjectStreamClass
2128                    // getDeclaredSUID: trailing `return Long.valueOf(...)`
2129                    // duplicated outside, real `return null` tail lost).
2130                    // A shared terminator at gend that the CATCH also copied
2131                    // (nestedTry) has a handler pred, is not absorbed, and
2132                    // must still be re-copied here — hence the all-preds-
2133                    // in-body mirror of the absorption rule.
2134                    let absorbed_tail = self.cfg.block_at(gend).filter(|&b| {
2135                        matches!(
2136                            self.results[b].term,
2137                            Term::Return(_) | Term::Throw(_)
2138                        ) && self.results[b].stmts.is_empty()
2139                            && !self.handler_group.contains_key(&b)
2140                            && !self.cfg.blocks[b].pred.is_empty()
2141                            && self.cfg.blocks[b]
2142                                .pred
2143                                .iter()
2144                                .all(|p| self.body_group.get(p) == Some(&gi))
2145                    });
2146                    // Handler-flow-only blocks (the handler's private tail,
2147                    // e.g. the synchronized monitorexit-rethrow `aload t;
2148                    // athrow`) are NOT shared method tails: the is_term
2149                    // exception below exists for tails the NORMAL path also
2150                    // reaches (nestedTry's lost return). Copying a
2151                    // handler-only pending-rethrow after the try leaks
2152                    // `throw <pending>;` into the normal flow (jdk26
2153                    // LambdaFormEditor.putInCache: 未报告的异常错误Throwable).
2154                    let hf_after = self.handler_flow_only(gi);
2155                    // Blocks are in ascending-start order. The scan STOPS at
2156                    // the first stop (barrier) block: when the block right
2157                    // after the span is the enclosing loop's exit, the flow
2158                    // LEAVES this scope there (the try body's Goto resolved
2159                    // to break) — continuing at some later non-stop block
2160                    // pulls post-exit code into the loop body, whose
2161                    // duplicated return then trips body_done and strands the
2162                    // exit's own tail (jdk26 Future.resultNow: the normal
2163                    // finally-if + `return result` after the retry loop went
2164                    // missing — 缺少返回语句).
2165                    let mut next: Option<usize> = None;
2166                    let dbg_next = crate::dbg_flag!("JCDC_DBG_SWF");
2167                    for nb in self.cfg.blocks.iter() {
2168                        if nb.start < gend {
2169                            continue;
2170                        }
2171                        if dbg_next {
2172                            eprintln!("NEXTSCAN gend={} nb={} start={} absorbed={} hf={} univ={} stop={} handler={} consumed={} term={}",
2173                                gend, nb.id, nb.start, Some(nb.id) == absorbed_tail,
2174                                hf_after.contains(&nb.id), ctx.universe.contains(&nb.id),
2175                                stop.contains(&nb.id), self.handler_group.contains_key(&nb.id),
2176                                ctx.consumed.contains(&nb.id),
2177                                matches!(self.results[nb.id].term, crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)));
2178                        }
2179                        if Some(nb.id) == absorbed_tail || hf_after.contains(&nb.id) {
2180                            continue;
2181                        }
2182                        if !ctx.universe.contains(&nb.id) {
2183                            continue;
2184                        }
2185                        if stop.contains(&nb.id) {
2186                            break;
2187                        }
2188                        let is_term = matches!(
2189                            self.results[nb.id].term,
2190                            Term::Return(_) | Term::Throw(_)
2191                        );
2192                        // A handler block is normally skipped (it belongs to
2193                        // the try's catch/finally), but a handler that is a
2194                        // shared return/throw is the method tail the finally
2195                        // copy jumps to — the normal (try-completes) path
2196                        // must still return it (nestedTry's lost return).
2197                        // THIS group's own handler is never that shared
2198                        // tail: its catch region already absorbed it, and
2199                        // re-walking it duplicated the handler as top-level
2200                        // statements after the try (jdk11
2201                        // ThreadedSeedGenerator.run: `Exception e = null;
2202                        // throw new InternalError(.., e);` after the
2203                        // catch-that-throws — 无法访问的语句).
2204                        if self.handler_group.contains_key(&nb.id)
2205                            && (!is_term || self.handler_group.get(&nb.id) == Some(&gi))
2206                        {
2207                            continue;
2208                        }
2209                        if ctx.consumed.contains(&nb.id) && !is_term {
2210                            continue;
2211                        }
2212                        next = Some(nb.id);
2213                        break;
2214                    }
2215                    match next {
2216                        // A consumed shared terminator right after the try is
2217                        // the method/region tail return that the catch also
2218                        // used; copy it inline so the normal (try-completes)
2219                        // path still returns (fixes nestedTry's lost return).
2220                        Some(nb) if ctx.consumed.contains(&nb) => {
2221                            parts.push(Region::CopyStmts { block: nb });
2222                            break;
2223                        }
2224                        Some(nb) => {
2225                            // Continue at the try's forward continuation even
2226                            // when it is not in this region's `reach` set: a
2227                            // try body reaches its tail through inlined/duplicated
2228                            // finally copies, so reachable_within (normal succ
2229                            // only) can miss the post-finally return block. The
2230                            // block is unconsumed and forward (start >= gend), so
2231                            // structuring it is monotone and recovers the tail
2232                            // return (nestedTry's `return sb.toString()`).
2233                            cur = nb;
2234                            last_via_goto = false;
2235                            continue;
2236                        }
2237                        None => break,
2238                    }
2239                }
2240            }
2241            let is_header =
2242                ctx.loop_headers.contains(&cur) && !self.handler_group.contains_key(&cur);
2243            if is_header {
2244                let members = ctx.loop_members.get(&cur).cloned().unwrap_or_default();
2245                // exits = EVERY member's out-edges leaving the loop: the
2246                // break-resolution set (a `break outer` from deep in the body
2247                // targets one of these; resolve_goto maps a Goto to the
2248                // OUTERMOST loop whose exits contain it -> labeled break).
2249                // A SWITCH-dispatch header never has a condition fall-out:
2250                // its out-of-loop target edges are per-case break stubs, so
2251                // extending exits cannot degrade a while-cond
2252                // classification (top-tested `while (i < n)` headers keep
2253                // their raw exits — String.contentEquals).
2254                let natural_follow_empty =
2255                    matches!(self.results[cur].term, crate::ir::build::Term::Switch { .. });
2256                let mut exits: Vec<usize> = Vec::new();
2257                for &m in members.iter() {
2258                    for &s in &self.cfg.blocks[m].succ {
2259                        if !members.contains(&s) && ctx.universe.contains(&s) && !exits.contains(&s)
2260                        {
2261                            exits.push(s);
2262                        }
2263                    }
2264                }
2265                // Bottom-tested loops ONLY (for(;;) / restart shapes): a
2266                // `break L` out of a deep switch compiles to a per-case
2267                // empty `goto TAIL` stub, so the member-boundary exits are
2268                // the stubs and the REAL continuation sits one hop behind
2269                // them — invisible to body_stop (the case region walks
2270                // through the stub and consumes the tail) and to the
2271                // post-loop continuation (stubs are consumed with
2272                // preds==header and filtered). See through pure-jump stubs
2273                // so exits name the semantic destination (jdk26
2274                // Pattern.sequence: `goto 609` stubs → the `if (head ==
2275                // null) return end; .. return head;` tail —
2276                // 缺少返回语句). Top-tested loops (String.contentEquals
2277                // `while (i < n)`) keep the raw exits: their header
2278                // fall-out exit drives the while-cond classification and
2279                // extending it degraded the loop to `while (true)` and
2280                // lost the post-loop return.
2281                if natural_follow_empty {
2282                    let mut extended: Vec<usize> = Vec::new();
2283                    for &x in exits.iter() {
2284                        let mut cur_x = x;
2285                        let mut guard = 0;
2286                        while matches!(self.results[cur_x].term, crate::ir::build::Term::Goto)
2287                            && self.results[cur_x].stmts.is_empty()
2288                            && self.cfg.blocks[cur_x].succ.len() == 1
2289                            && guard < 8
2290                        {
2291                            let n = self.cfg.blocks[cur_x].succ[0];
2292                            if members.contains(&n)
2293                                || !ctx.universe.contains(&n)
2294                                || ctx.loop_stack.contains(&n)
2295                                || ctx.loop_headers.contains(&n)
2296                            {
2297                                break;
2298                            }
2299                            cur_x = n;
2300                            guard += 1;
2301                        }
2302                        if !extended.contains(&cur_x) {
2303                            extended.push(cur_x);
2304                        }
2305                    }
2306                    exits = extended;
2307                }
2308                exits.sort_by_key(|e| self.cfg.blocks[*e].start);
2309                // natural_follow = the HEADER's own exits (the condition's
2310                // fall-out): where straight-line flow continues after the
2311                // loop. Other members' exits are body breaks and must NOT be
2312                // the continuation -- exits.first() over all members could be
2313                // a far-away early-return the body region never reaches,
2314                // silently dropping the post-loop tail (Integer.toString).
2315                let mut natural_follow: Vec<usize> = Vec::new();
2316                for &s in &self.cfg.blocks[cur].succ {
2317                    if !members.contains(&s) && ctx.universe.contains(&s) {
2318                        natural_follow.push(s);
2319                    }
2320                }
2321                // A top-tested Cond header's own fall-out is a LIVE normal
2322                // completion no matter what the body does: body_done (every
2323                // body path returns/continues) must not suppress it
2324                // (jdk11/17/26 AbstractPoller$Request.awaitResult: the
2325                // wait-retry body is all-continue, so the body_done veto
2326                // dropped the `!interrupted ? result : interrupt+result`
2327                // epilogue the condition falls into — 缺少返回语句 ×3
2328                // trees, latent behind OCSPResponse's FLOW mask). The
2329                // bottom-tested fallback below keeps the veto: for(;;)/
2330                // do-while exits are break landings the veto legitimately
2331                // owns (TempFileHelper.create's in-try areturn re-emission).
2332                let cond_exit_live = !natural_follow.is_empty()
2333                    && matches!(self.results[cur].term, Term::Cond { .. });
2334                if natural_follow.is_empty() {
2335                    // Bottom-tested loop (do-while): the header is the body
2336                    // entry whose only successor is inside the loop; the
2337                    // natural exit hangs off the condition member instead.
2338                    natural_follow = exits.clone();
2339                }
2340                natural_follow.sort_by_key(|e| self.cfg.blocks[*e].start);
2341                // Body: structure from the header; the loop's exits and the
2342                // enclosing stop bound it. The header is the body entry and is
2343                // NOT in the body stop (the back edge re-targets it -> Goto ->
2344                // `continue` at conversion).
2345                let mut body_stop: HashSet<usize> = stop.iter().copied().collect();
2346                body_stop.extend(exits.iter().copied());
2347                let header = cur;
2348                // Structure the body from the header, but drop the header from
2349                // loop_headers for the duration so the body region does not
2350                // re-detect it as a loop (infinite recursion). The back edge
2351                // (a body block whose succ is the header) hits the consumed
2352                // check -> Goto{header} -> `continue` at conversion.
2353                ctx.loop_headers.remove(&header);
2354                ctx.loop_stack.push(header);
2355                // Sync the walk-facing loops_stack so any walk-based sub-builder
2356                // reused inside the body (structure_try / structure_switch)
2357                // resolves a back-edge to this SESE loop header as a
2358                // `continue` (Goto{header}) instead of re-walking it as a new
2359                // nested loop (the catch-handler `while(true)` duplication).
2360                self.loops_stack.push(header);
2361                // The loop tail (back-edge source -> header) is reachable
2362                // from the header WITHOUT stepping on an exit only through
2363                // the body, so reachable_within(header, body_stop) always
2364                // contains every member; blocks of divergent shapes (a
2365                // pre-loop init guard whose branches rejoin after the loop,
2366                // Integer.toString blk9/10) are correctly NOT members. Pass
2367                // this exact set as the body sub-scope so nested copy_walks
2368                // (which bound their region by reachable_within) cannot lose
2369                // the post-loop tail.
2370                let body_scope = reachable_within(self.cfg, header, &body_stop);
2371                let body = self.sese_region_with_scope(header, &body_stop, body_scope, ctx);
2372                self.loops_stack.pop();
2373                ctx.loop_stack.pop();
2374                // A body that cannot complete normally (both arms of its
2375                // tail check inline the loop's terminator exits — the
2376                // finally-retry dual-throw shape, jdk11 ObjectInputStream
2377                // .readSerialData) makes the post-loop continuation
2378                // unreachable (无法访问的语句): emit the loop and stop.
2379                let handler_exits: Vec<usize> = exits
2380                    .iter()
2381                    .copied()
2382                    .filter(|e| self.is_handler(*e))
2383                    .collect();
2384                // Mirrors conv's break resolutions: a Goto to a loop exit
2385                // or to any block that cannot reach back to the header
2386                // converts to `break` — the loop is escapable.
2387                fn region_has_break_goto(
2388                    r: &Region,
2389                    s: &Structurer,
2390                    header: usize,
2391                    exits: &[usize],
2392                    members: &HashSet<usize>,
2393                ) -> bool {
2394                    let esc = |t: usize| {
2395                        if t == header || exits.contains(&t) {
2396                            return t != header;
2397                        }
2398                        // A jump onto another MEMBER is an internal
2399                        // landing (an inner construct's break/merge whose
2400                        // statements render mid-body — TempFileHelper
2401                        // .create's inner generatePath loop exit
2402                        // `goto 28`); it never leaves THIS loop, so it
2403                        // cannot make the loop completable.
2404                        if members.contains(&t) {
2405                            return false;
2406                        }
2407                        // A forward escape onto a shared return/throw
2408                        // terminator is NOT a break: conversion inlines
2409                        // the terminator at the arrival (if-follow inline
2410                        // or RawGoto term-copy), so the path completes
2411                        // abruptly right here (TempFileHelper.create's SE
2412                        // catch `if (dir != tmpdir) <goto throw-e>` — the
2413                        // old esc made body_done miss the non-completing
2414                        // for(;;) and the chain re-emitted an in-try
2415                        // areturn after the loop — 无法访问的语句 x2).
2416                        !matches!(
2417                            s.results[t].term,
2418                            crate::ir::build::Term::Return(_) | crate::ir::build::Term::Throw(_)
2419                        ) && !crate::structure::can_reach_cfg(s.cfg, t, header, 4096)
2420                    };
2421                    match r {
2422                        Region::Goto { target } => esc(*target),
2423                        Region::Seq(v) => v
2424                            .iter()
2425                            .any(|x| region_has_break_goto(x, s, header, exits, members)),
2426                        Region::If { then_r, else_r, .. } => {
2427                            region_has_break_goto(then_r, s, header, exits, members)
2428                                || region_has_break_goto(else_r, s, header, exits, members)
2429                        }
2430                        Region::Loop { body, .. } => {
2431                            region_has_break_goto(body, s, header, exits, members)
2432                        }
2433                        Region::Try { body, catches, .. } => {
2434                            region_has_break_goto(body, s, header, exits, members)
2435                                || catches.iter().any(|(_, _, cr)| {
2436                                    region_has_break_goto(cr, s, header, exits, members)
2437                                })
2438                        }
2439                        Region::Switch { cases, default, .. } => {
2440                            cases
2441                                .iter()
2442                                .any(|(_, cr)| region_has_break_goto(cr, s, header, exits, members))
2443                                || default
2444                                    .as_ref()
2445                                    .map(|d| region_has_break_goto(d, s, header, exits, members))
2446                                    .unwrap_or(false)
2447                        }
2448                        _ => false,
2449                    }
2450                }
2451                // A `break` inside the body means the loop CAN complete
2452                // normally: the post-loop follow is reachable through it
2453                // and must still be structured. region_terminates_ex is
2454                // Goto-blind (conservatively false for Goto parts), but a
2455                // body that absorbed an exit-landing return tail
2456                // (awaitTermination's `var7 = false; unlock; return var7;`
2457                // return-false path became the in-body continuation of the
2458                // header try) looks terminating while the state-test break
2459                // escapes — suppressing natural_follow there stranded the
2460                // return-true tail after the loop (缺少返回语句, jdk11/17
2461                // ThreadPoolExecutor.awaitTermination).
2462                // Loop-aware completion: a Goto back to the header is a
2463                // `continue` — abrupt, and it never lands past the loop —
2464                // so it counts as terminating HERE (the FAE retry catch of
2465                // TempFileHelper.create's for(;;); region_terminates_ex
2466                // cannot know the header).
2467                fn region_done_or_continues(
2468                    r: &Region,
2469                    s: &Structurer,
2470                    handler_exits: &[usize],
2471                    header: usize,
2472                ) -> bool {
2473                    match r {
2474                        Region::Goto { target } => {
2475                            *target == header
2476                                || crate::structure::region_terminates_ex(
2477                                    r,
2478                                    s.results,
2479                                    handler_exits,
2480                                )
2481                        }
2482                        Region::Seq(v) => v
2483                            .last()
2484                            .map(|x| region_done_or_continues(x, s, handler_exits, header))
2485                            .unwrap_or(false),
2486                        Region::If { then_r, else_r, .. } => {
2487                            region_done_or_continues(then_r, s, handler_exits, header)
2488                                && region_done_or_continues(else_r, s, handler_exits, header)
2489                        }
2490                        Region::Try { body, catches, .. } => {
2491                            region_done_or_continues(body, s, handler_exits, header)
2492                                && catches.iter().all(|(_, _, c)| {
2493                                    region_done_or_continues(c, s, handler_exits, header)
2494                                })
2495                        }
2496                        other => crate::structure::region_terminates_ex(
2497                            other,
2498                            s.results,
2499                            handler_exits,
2500                        ),
2501                    }
2502                }
2503                let body_done = region_done_or_continues(&body, self, &handler_exits, header)
2504                    && !region_has_break_goto(&body, self, header, &exits, &members);
2505                if crate::dbg_flag!("JCDC_DBG_SESE") {
2506                    eprintln!("BODYDONE header={} done={} term={} brk={} exits={:?} body={:#?}",
2507                        header, body_done,
2508                        region_done_or_continues(&body, self, &handler_exits, header),
2509                        region_has_break_goto(&body, self, header, &exits, &members),
2510                        exits, body);
2511                }
2512                if is_header {
2513                    ctx.loop_headers.insert(header);
2514                }
2515                for &m in members.iter() {
2516                    ctx.consumed.insert(m);
2517                }
2518                let mut body = body;
2519                if !crate::dbg_flag!("JCDC_NO_MATEXIT") {
2520                    // The header's own out-of-member successors are the
2521                    // NATURAL exits (the while-cond fall-out / bottom-test
2522                    // exit): the parent region renders them ONCE after the
2523                    // Loop. Materializing them inside the body rotated
2524                    // while-cond loops into while(true)+copied-break
2525                    // (TempFileHelper/HttpURLConnection clinit churn).
2526                    let mut natural: HashSet<usize> = self.cfg.blocks[header]
2527                        .succ
2528                        .iter()
2529                        .copied()
2530                        .filter(|x| !members.contains(x) && *x != header)
2531                        .collect();
2532                    // SESE's computed natural_follow is ALSO parent-owned
2533                    // continuation (it may differ from the header's raw
2534                    // succs — bottom-tested/exit-derived follows): splicing
2535                    // it copied UnixPath.normalize's whole join-and-return
2536                    // continuation into a break arm (nf=[36], 缺少返回语句
2537                    // x2 trees).
2538                    natural.extend(natural_follow.iter().copied());
2539                    self.materialize_content_exits(
2540                        &mut body,
2541                        header,
2542                        &exits,
2543                        &members,
2544                        &natural,
2545                        &ctx.universe,
2546                        &ctx.loop_stack,
2547                        &ctx.loop_headers,
2548                        &ctx.top_groups,
2549                        stop,
2550                        &reach,
2551                        &ctx.consumed,
2552                        natural_follow.is_empty(),
2553                    );
2554                }
2555                parts.push(Region::Loop { header, body: Box::new(body), members: members.clone(), exits: exits.clone() });
2556                // Continue after the loop at the header's natural exit (if it
2557                // is within this region's reach and not an enclosing stop).
2558                // A consumed candidate whose preds are ALL the header can
2559                // only have been emitted INSIDE the body (the header's
2560                // switch/if dispatched it — the restart loop's natural exits
2561                // ARE the case targets); hoisting it re-emits the first
2562                // case's body after the loop (jdk26 DecimalFormat
2563                // `Long l = ...` tail). A consumed candidate with other
2564                // preds is the genuine shared follow (Class.methodToString
2565                // `if (c) return X; else { loop }`) and keeps the loop-top
2566                // consumed policy below.
2567                let header_id = cur;
2568                // Two-level loop exits: `break LOOP` often compiles to a
2569                // dedicated `goto TAIL` stub, so the member-boundary exit
2570                // is the STUB, not the real continuation. When a stub exit
2571                // was consumed by the body walk (the switch case's Goto
2572                // resolved against it) and chains forward — through further
2573                // consumed single-succ stubs — to an unconsumed non-member
2574                // block, THAT block is the true post-loop follow (jdk26
2575                // Pattern.sequence: exits {goto-609 stubs, throw} all
2576                // consumed, the `if (head == null) return end; .. return
2577                // head;` tail stranded → 缺少返回语句).
2578                // Gate: ONLY when every candidate exit was consumed by the
2579                // body walk — the deep-switch break-stub shape (Pattern
2580                // .sequence: all exits are per-case `goto TAIL` stubs the
2581                // case regions absorbed, and the shared TAIL behind them is
2582                // the true continuation). A normal loop leaves its header
2583                // fall-out unconsumed; chain-walking it anyway degraded
2584                // String.contentEquals's `while (i < n)` classification and
2585                // dropped the post-loop `return true`.
2586                let all_consumed = !natural_follow.is_empty()
2587                    && natural_follow.iter().all(|f| ctx.consumed.contains(f));
2588                if crate::dbg_flag!("JCDC_DBG_SESE") && all_consumed {
2589                    eprintln!("CHAINWALK header={} nf={:?} succs={:?} cons49={:?}", header_id,
2590                        natural_follow,
2591                        natural_follow.iter().map(|f| self.cfg.blocks[*f].succ.clone()).collect::<Vec<_>>(),
2592                        natural_follow.iter().map(|f| ctx.consumed.contains(f)).collect::<Vec<_>>());
2593                }
2594                let natural_follow: Vec<usize> = if all_consumed {
2595                    natural_follow
2596                        .into_iter()
2597                        .map(|x| {
2598                            let mut cur_x = x;
2599                            let mut guard = 0;
2600                            while ctx.consumed.contains(&cur_x)
2601                                && self.cfg.blocks[cur_x].succ.len() == 1
2602                                && guard < 16
2603                            {
2604                                let n = self.cfg.blocks[cur_x].succ[0];
2605                                if ctx.consumed.contains(&n)
2606                                    || stop.contains(&n)
2607                                    || members.contains(&n)
2608                                    || ctx.loop_stack.contains(&n)
2609                                    || ctx.loop_headers.contains(&n)
2610                                {
2611                                    break;
2612                                }
2613                                cur_x = n;
2614                                guard += 1;
2615                            }
2616                            cur_x
2617                        })
2618                        .collect::<Vec<_>>()
2619                } else {
2620                    natural_follow
2621                };
2622                let natural_follow = natural_follow
2623                    .into_iter()
2624                    .filter(|f| {
2625                        // A top-tested Cond header's own fall-out stays the
2626                        // continuation even when the body walk consumed it
2627                        // (AbstractPoller$Request.awaitResult: the epilogue
2628                        // rode into the body as the inner retry loop's
2629                        // follow; dropping it left the condition-fall path
2630                        // falling off the method — 缺少返回语句 ×3). The
2631                        // restart-loop case-target policy only owns
2632                        // unconditional (for(;;)/Goto) headers.
2633                        cond_exit_live
2634                            || !ctx.consumed.contains(f)
2635                            || !self.cfg.blocks[*f]
2636                                .pred
2637                                .iter()
2638                                .all(|&p| p == header_id)
2639                    })
2640                    .collect::<Vec<_>>();
2641                if crate::dbg_flag!("JCDC_DBG_SESE") {
2642                    eprintln!("LOOPFOLLOW header={} exits={:?} natural_follow={:?} consumed_nf={:?}",
2643                        header_id, exits, natural_follow,
2644                        natural_follow.iter().map(|f| ctx.consumed.contains(f)).collect::<Vec<_>>());
2645                }
2646                if body_done && !cond_exit_live {
2647                    ctx.depth -= 1;
2648                    return match parts.len() {
2649                        0 => Region::Empty,
2650                        1 => parts.into_iter().next().unwrap(),
2651                        _ => Region::Seq(parts),
2652                    };
2653                }
2654                let follow_pick = match natural_follow.first().copied() {
2655                    // No `!consumed` gate: a follow already consumed by a
2656                    // sibling branch (the shared single-return block after
2657                    // `if (c) return X; else { loop }`, Class.methodToString)
2658                    // is handled by the loop-top consumed policy — a shared
2659                    // Return/Throw gets CopyStmts (inlined at this arrival),
2660                    // a header becomes `continue`, anything else copy_walk/
2661                    // Goto. Gating on !consumed silently dropped the else
2662                    // path's return (missing-return compile error; walk
2663                    // keeps the tail).
2664                    //
2665                    // body_done vetoes UNLESS the header's own condition
2666                    // fall-out is live (cond_exit_live): a top-tested
2667                    // while's condition exit is a normal completion even
2668                    // when every body path continues/returns
2669                    // (AbstractPoller$Request.awaitResult). The veto stays
2670                    // for the bottom-tested fallback's in-try areturn exits
2671                    // (the shared terminator CopyStmts policy re-emitted
2672                    // `return Files.createDirectory(..)` after the
2673                    // non-completing for(;;) — TempFileHelper.create
2674                    // 无法访问的语句 x2 trees).
2675                    Some(f) if (!body_done || cond_exit_live)
2676                        && !stop.contains(&f)
2677                        && reach.contains(&f) => Some(f),
2678                    _ => None,
2679                };
2680                let follow_pick = if body_done && !cond_exit_live { None } else { follow_pick.or_else(|| {
2681                    // Stranded break landing: an exit some in-body
2682                    // `break L` targeted but neither the body nor any
2683                    // follow consumed (a guarded-pattern case body sits
2684                    // outside the switch dispatch — jdk26
2685                    // NumberFormat.format's `case BigInteger bi when
2686                    // bi.bitLength() < 64` break-L1 landed on nothing:
2687                    // natural_follow was empty and the bi.longValue()
2688                    // body block was never structured — 缺少返回语句 x2
2689                    // methods).
2690                    // Pre-stub-filter candidate set for the
2691                    // shared-collector test (see the walk-side twin in
2692                    // structure.rs next_after_loop).
2693                    let claim_cands: HashSet<usize> = exits
2694                        .iter()
2695                        .copied()
2696                        .filter(|e| {
2697                            !ctx.consumed.contains(e)
2698                                && !stop.contains(e)
2699                                && ctx.universe.contains(e)
2700                                && reach.contains(e)
2701                                && !self.is_handler(*e)
2702                                && *e != header_id
2703                        })
2704                        .collect();
2705                    let mut cands: Vec<usize> = exits
2706                        .iter()
2707                        .copied()
2708                        .filter(|e| {
2709                            !ctx.consumed.contains(e)
2710                                && !stop.contains(e)
2711                                && ctx.universe.contains(e)
2712                                && reach.contains(e)
2713                                && !self.is_handler(*e)
2714                                && *e != header_id
2715                                // Continue-stub exits (statement-free jump
2716                                // chains back into an enclosing loop) are
2717                                // conditional break arms of the inner loop,
2718                                // already resolved inside the body — walking
2719                                // one as the continuation re-emits the jump
2720                                // unconditionally (jdk11
2721                                // FutureTask.removeWaiter `continue retry`
2722                                // stubs became an unconditional continue
2723                                // after the inner while — 无法访问的语句 on
2724                                // the tail return, x3 trees).
2725                                && !ctx.loop_stack.iter().any(|h| {
2726                                    h != e
2727                                        && self.is_stmt_free_chain_to_block(*e, *h)
2728                                        && !self.stub_chain_claims_continuation(
2729                                            *e,
2730                                            *h,
2731                                            &claim_cands,
2732                                        )
2733                                })
2734                        })
2735                        .collect();
2736                    cands.sort_by_key(|e| self.cfg.blocks[*e].start);
2737                    cands.first().copied()
2738                }) };
2739                match follow_pick {
2740                    Some(f) => {
2741                        cur = f;
2742                        continue;
2743                    }
2744                    None => {
2745                        // An exit that is an ENCLOSING loop's barrier must
2746                        // be materialized as a Goto (the labeled break of
2747                        // the enclosing loop); silently ending the body
2748                        // makes the inner loop's normal completion
2749                        // re-enter the outer (jdk11
2750                        // FutureTask.removeWaiter — see the walk-side
2751                        // twin in structure.rs).
2752                        if !body_done {
2753                            let barrier_exit = exits
2754                                .iter()
2755                                .copied()
2756                                .filter(|e| {
2757                                    stop.contains(e)
2758                                        && !ctx.loop_stack.contains(e)
2759                                        && ctx.universe.contains(e)
2760                                        && !self.is_handler(*e)
2761                                })
2762                                .min_by_key(|e| self.cfg.blocks[*e].start);
2763                            if let Some(e) = barrier_exit {
2764                                parts.push(Region::Goto { target: e });
2765                            }
2766                        }
2767                        break;
2768                    }
2769                }
2770            }
2771
2772            ctx.consumed.insert(cur);
2773            match self.results[cur].term.clone() {
2774                Term::Cond { cond } => {
2775                    let succs = self.cfg.blocks[cur].succ.clone();
2776                    if succs.len() != 2 {
2777                        parts.push(Region::Basic { block: cur });
2778                        match succs.first().copied() {
2779                            Some(n) => {
2780                                cur = n;
2781                                continue;
2782                            }
2783                            None => break,
2784                        }
2785                    }
2786                    let (fall, taken) = (succs[0], succs[1]);
2787                    // Follow = true immediate post-dominator; when None (the
2788                    // branches diverge to different terminals) fall back to
2789                    // convergent_merge, which recovers a shared loop tail
2790                    // (Legacy6 `if (a==2 && b==2) break outer;`) but REFUSES
2791                    // candidates dominating a branch target — ancestor
2792                    // confluentes like `if (A || B) fixup;` (Integer.toString)
2793                    // keep follow=None so the branches are walked naturally
2794                    // and the re-reached continuation is duplicated by
2795                    // copy_walk (walk-parity shape).
2796                    let follow = self
2797                        .sese_ipdom(ctx, cur)
2798                        .filter(|f| ctx.universe.contains(f) && !stop.contains(f))
2799                        .or_else(|| self.live_merge(ctx, &[taken, fall], stop))
2800                        // A follow that IS one of the branch targets only
2801                        // works when the OTHER target flows to it — that
2802                        // branch completes normally and falls through
2803                        // (empty branch). When the sibling instead exits
2804                        // (return/throw) or escapes to a stop, the target
2805                        // is shared-tail code: choosing it as follow
2806                        // leaves the tail unstructured after the sibling
2807                        // consumes it (Fin/Long$LongCache: lost
2808                        // `cache = archivedCache;` tail => final
2809                        // double-assign in the branch copy / "variable f
2810                        // might not have been initialized"). follow=None
2811                        // keeps the tail inside the branch (walk parity).
2812                        .filter(|f| {
2813                            ![taken, fall].contains(f)
2814                                || [taken, fall].iter().any(|&t| {
2815                                    t != *f
2816                                        && (stop.contains(&t)
2817                                            || self.reaches_within(ctx, t, *f, stop))
2818                                })
2819                        })
2820                        // A shared TERMINATOR as follow means some branch
2821                        // never completes normally — it must be a branch that
2822                        // flows to f within this region's stop set. When a
2823                        // target instead escapes to a stop (a jump OUT: the
2824                        // enclosing if-follow / loop exit), f post-dominates
2825                        // only through the other branch, and choosing it
2826                        // elides that branch's terminator as "natural
2827                        // fall-through" while re-emitting it AFTER the if —
2828                        // jdk17 String(byte[],int,int,Charset) lost the
2829                        // LATIN1 fast-path `return;` (goto 839 = shared
2830                        // ctor-exit) inside `else { assigns }`, double-
2831                        // assigning the final fields ("variable value might
2832                        // already have been assigned") and blocking every
2833                        // jdk17 corpus family. follow=None keeps the return
2834                        // inside the branch (walk-parity).
2835                        .filter(|f| {
2836                            !matches!(self.results[*f].term, Term::Return(_) | Term::Throw(_))
2837                                || [taken, fall].iter().all(|&t| {
2838                                    stop.contains(&t)
2839                                        || ctx.consumed.contains(&t)
2840                                        || self.reaches_within(ctx, t, *f, stop)
2841                                })
2842                        })
2843                        // The convergent shortcut result gets the same two
2844                        // screens as the live_merge result: a branch-target
2845                        // follow needs a sibling route (reaches) and a
2846                        // shared-terminator follow needs every target to
2847                        // flow to it. (Empirically census-neutral; the
2848                        // stronger all_routes_via variant of screen #1 is
2849                        // documented-FAILED in memory
2850                        // ocsp-sese-follow-diagnosis — it trades OCSPResponse
2851                        // against BasicImageReader/keytool codegen cliffs.)
2852                        .or_else(|| self.convergent_merge(ctx, &[taken, fall], stop))
2853                        .filter(|f| {
2854                            ![taken, fall].contains(f)
2855                                || [taken, fall].iter().any(|&t| {
2856                                    t != *f
2857                                        && (stop.contains(&t)
2858                                            || self.reaches_within(ctx, t, *f, stop))
2859                                })
2860                        })
2861                        .filter(|f| {
2862                            !matches!(self.results[*f].term, Term::Return(_) | Term::Throw(_))
2863                                || [taken, fall].iter().all(|&t| {
2864                                    stop.contains(&t)
2865                                        || ctx.consumed.contains(&t)
2866                                        || self.reaches_within(ctx, t, *f, stop)
2867                                })
2868                        });
2869                    // FALSE-MERGE RECONCILIATION against the walk-side
2870                    // picker: a branch-target follow is exactly the class
2871                    // immediate_postdom's successor-candidate rejection
2872                    // was hardened against (OCSPResponse's parked
2873                    // UNSPECIFIED block — its sibling's values[reason]
2874                    // route bypasses it into the post-flow; the walk
2875                    // renders the golden arm-exclusive copies via this
2876                    // rejection). When the walk picker disagrees with the
2877                    // SESE resolvers, defer to it: adopt its confluence
2878                    // (BasicImageReader's `map!=null && MAP_ALL` chain —
2879                    // the shared else-arm block is a false merge whose
2880                    // real confluence is the third-conjunct test) or
2881                    // reject to None when it finds no merge at all. The
2882                    // walk's own gates (is_fall, same_body, group-owned
2883                    // exemption, stmt-free stubs, depth-capped bypass
2884                    // probe, big-tail exception) come along verbatim.
2885                    let recon_enabled = !crate::dbg_flag!("JCDC_NO_RECON");
2886                    let follow = match follow {
2887                        Some(f) if recon_enabled && (f == taken || f == fall)
2888                            // Switch-case territory keeps its own follow
2889                            // discipline: walk's picker REJECTS the case
2890                            // epilogue targets on purpose so each arm gets
2891                            // per-arrival try copies, while SESE parks them
2892                            // under the confluence bar (keytool doCommands'
2893                            // 28-pred TWR epilogue — deferring to the walk
2894                            // picker there re-armed the 12x copy storm,
2895                            // try 语句的代码过长 x525).
2896                            && self.switch_depth == 0
2897                            // Many-pred tail confluences are owned by the
2898                            // SESE copy/confluence-bar machinery for the
2899                            // same reason.
2900                            && !self.shared_tail_confluence(f)
2901                            // Only MULTI-PRED targets can be false merges:
2902                            // the pathology is arm CONTENT shared by both
2903                            // branch routes (OCSPResponse's UNSPECIFIED
2904                            // block has the two range-check arms as preds;
2905                            // BasicImageReader's channel-open else-arm has
2906                            // both conjunct tests). A SINGLE-pred taken
2907                            // target is an ordinary if-else-if chain arm
2908                            // entry whose parking IS the dispatch shape
2909                            // (keytool doCommands' command bodies — every
2910                            // reconciliation there, even pure-stub
2911                            // adoption, re-armed the epilogue copy storm).
2912                            && self.cfg.blocks[f].pred.len() >= 2
2913                            // AND the parked target must carry STATEMENTS:
2914                            // parking a statement-free test head is the
2915                            // healthy if-else-if chain flattening (keytool
2916                            // doGenCert's dispatch chain), not arm-content
2917                            // parking — there is nothing to cross.
2918                            && !self.results[f].stmts.is_empty()
2919                            // AND the sibling must actually BYPASS the
2920                            // parked target (a normal-completing route
2921                            // into its post-flow — the crossing that
2922                            // breaks the empty-arm contract). Without a
2923                            // bypass the parking is sound (every sibling
2924                            // route flows through the target and the empty
2925                            // arm falls into its emission correctly) and
2926                            // the SESE choice stands (keytool doPrintEntry
2927                            // dispatch parking).
2928                            && ![taken, fall].iter().any(|&t| {
2929                                t != f
2930                                    && (stop.contains(&t)
2931                                        || self.all_routes_via(ctx, t, f, stop))
2932                            }) =>
2933                        {
2934                            let w = self.postdom_ipdom(&ctx.universe, cur);
2935                            match w {
2936                                Some(x) if x != f
2937                                    && self.results[x].stmts.is_empty()
2938                                    && self.results[x].out_stack.is_empty()
2939                                    && self.cfg.blocks[x].succ.len() == 1
2940                                    && matches!(
2941                                        self.results[x].term,
2942                                        Term::Goto | Term::Fallthrough
2943                                    ) =>
2944                                {
2945                                    // Adopt only PURE GOTO/FALL STUB
2946                                    // confluentes: in the arm-content
2947                                    // parking pathology the false merge's
2948                                    // real continuation is the transparent
2949                                    // stub past the parked statements
2950                                    // (OCSPResponse cur=5: w=9, the
2951                                    // `goto 166` stub after the parked
2952                                    // UNSPECIFIED block — adopting it puts
2953                                    // the parked block in BOTH arms as
2954                                    // per-arrival copies, the golden
2955                                    // walk-parity shape). Statement-bearing
2956                                    // or test-head confluentes are
2957                                    // case-arm/dispatch entries whose
2958                                    // adoption dissolves the SESE
2959                                    // parked-arm architecture (keytool
2960                                    // doCommands: every broader adoption
2961                                    // re-armed the epilogue copy storm,
2962                                    // try 语句的代码过长 x524-540).
2963                                    if crate::dbg_flag!("JCDC_DBG_WPD") {
2964                                        eprintln!("WPD cur={} taken={} fall={} f={} ADOPT-STUB w={}", cur, taken, fall, f, x);
2965                                    }
2966                                    if ctx.universe.contains(&x)
2967                                        && !stop.contains(&x)
2968                                        && !ctx.consumed.contains(&x)
2969                                        && !ctx.loop_stack.contains(&x)
2970                                        // The stub must not be a loop
2971                                        // CONTINUE trampoline (backward
2972                                        // goto into a header): adopting it
2973                                        // as the follow re-routes the chain
2974                                        // into loop machinery (keytool
2975                                        // doGenCert's `goto 5` back-edge
2976                                        // stub).
2977                                        && !self.cfg.blocks[x].succ.iter().any(|&s| {
2978                                            ctx.loop_headers.contains(&s)
2979                                                && self.cfg.blocks[s].start
2980                                                    <= self.cfg.blocks[x].start
2981                                        })
2982                                    {
2983                                        Some(x)
2984                                    } else {
2985                                        Some(f)
2986                                    }
2987                                }
2988                                // w == None (walk rejects every confluence),
2989                                // w == f, or w not a pure stub: keep the
2990                                // SESE choice — walk's rejection/selection
2991                                // cues its own guard-walk architecture, not
2992                                // SESE's parked-follow + confluence-bar one.
2993                                _ => Some(f),
2994                            }
2995                        }
2996                        other => other,
2997                    };
2998                    let mut bstop: HashSet<usize> = stop.iter().copied().collect();
2999                    if let Some(f) = follow {
3000                        bstop.insert(f);
3001                    }
3002                    let then_r = self.sese_region(taken, &bstop, ctx);
3003                    let else_r = self.sese_region(fall, &bstop, ctx);
3004                    // Value-diamond / ternary: both branches are pure single
3005                    // pushes that merge — reuse the builder's folded merge so
3006                    // the ternary is recovered (parity with walk).
3007                    let ternary = self.sese_ternary(cur, taken, fall, follow, &then_r, &else_r);
3008                    parts.push(Region::If {
3009                        block: cur,
3010                        cond,
3011                        then_r: Box::new(then_r),
3012                        else_r: Box::new(else_r),
3013                        follow,
3014                        ternary,
3015                    });
3016                    match follow {
3017                        // A follow already consumed by the case regions (the
3018                        // shared terminator tail each case absorbed/duplicated)
3019                        // must not continue here — that would re-emit it after
3020                        // the switch.
3021                        Some(f) if reach.contains(&f) && !ctx.consumed.contains(&f) => {
3022                            cur = f;
3023                            last_via_goto = false;
3024                            continue;
3025                        }
3026                        _ => break,
3027                    }
3028                }
3029                Term::Goto | Term::Fallthrough => {
3030                    parts.push(Region::Basic { block: cur });
3031                    last_via_goto = matches!(self.results[cur].term, Term::Goto);
3032                    match self.cfg.blocks[cur].succ.first().copied() {
3033                        Some(n) => {
3034                            cur = n;
3035                            continue;
3036                        }
3037                        None => break,
3038                    }
3039                }
3040                Term::Return(_) | Term::Throw(_) => {
3041                    parts.push(Region::Basic { block: cur });
3042                    break;
3043                }
3044                Term::Switch { selector, targets , default: _ } => {
3045                    // Reuse the tuned switch region builder (case fall-through,
3046                    // per-case epilogues). Case bodies are structured by walk
3047                    // over the case sub-scope; `consumed` is passed as the
3048                    // claimed set so SESE and the switch share consumption.
3049                    //
3050                    // follow = ipdom, else the convergent merge of all case
3051                    // targets. Inside a loop the shared tail (`++i`, back-edge)
3052                    // post-dominates the dispatch only at the virtual exit, so
3053                    // ipdom is None; convergent_merge recovers the tail as the
3054                    // follow so every case `break`s to it and it is structured
3055                    // ONCE after the switch (EnumSwitch — otherwise the first
3056                    // case swallows the tail as `++i; continue` and the rest
3057                    // spin forever).
3058                    let succs = self.cfg.blocks[cur].succ.clone();
3059                    if crate::dbg_flag!("JCDC_DBG_GOTO") {
3060                        eprintln!("SWFOLLOW cur={} ipdom={:?} vx={} succs={:?} stop={:?}",
3061                            cur, ctx.ipdom.get(&cur), ctx.vx, succs, stop);
3062                    }
3063                    let follow = self
3064                        .sese_ipdom(ctx, cur)
3065                        .filter(|f| !stop.contains(f))
3066                        // A switch inside a loop body whose cases all end in `break`
3067                        // merges at the loop increment (a body_stop exit),
3068                        // and a throwing default kills the plain ipdom —
3069                        // live_merge recovers it (ObjectStreamClass
3070                        // .computeFieldOffsets: fall-through cases and
3071                        // dangling break labels).
3072                        .or_else(|| self.live_merge(ctx, &succs, stop))
3073                        .or_else(|| self.convergent_merge(ctx, &succs, stop))
3074                        // Copied/shared-tail regions: the confluence lives
3075                        // in `stop` (enclosing flow owns it) — still the
3076                        // logical follow for case `break` resolution (jdk11
3077                        // Calendar.createCalendar catch-copy switch).
3078                        .or_else(|| self.switch_stop_confluence(cur, &ctx.universe, stop))
3079                        // Loop-tail epilogue: cases break the switch into a
3080                        // shared tail that flows back to the header; the
3081                        // forward resolvers refuse header-reaching paths
3082                        // (jdk26 Pattern.sequence lost the epilogue and the
3083                        // post-loop tail -> 缺少返回语句).
3084                        .or_else(|| self.loop_tail_switch_follow(ctx, cur, stop));
3085                    if crate::dbg_flag!("JCDC_DBG_SWF") {
3086                        eprintln!("SWF-RESOLVED cur={} follow={:?}", cur, follow);
3087                    }
3088                    // CROSSING follow: see the walk-side twin in
3089                    // structure.rs — a nested switch's stop-confluence
3090                    // belongs to an enclosing construct; region-level
3091                    // follow stays None so conversion binds the case-tail
3092                    // goto to the OWNER (labeled break), while `stop`
3093                    // still bounds the case walks at the confluence.
3094                    // Bindable exception (see switch_follow_bindable):
3095                    // when every route past the switch lands on the
3096                    // confluence anyway, rebuild with the follow so the
3097                    // arms get plain breaks (ClassPrinterImpl.toYaml/toXml
3098                    // 无法访问的语句 x2 trees).
3099                    let crossing = match follow {
3100                        Some(f) if stop.contains(&f) && self.switch_depth > 0 => Some(f),
3101                        _ => None,
3102                    };
3103                    let region_follow = match crossing {
3104                        Some(_) => None,
3105                        None => follow,
3106                    };
3107                    let mut claimed = ctx.consumed.clone();
3108                    self.switch_depth += 1;
3109                    let mut sw = self.structure_switch(
3110                        cur,
3111                        selector.clone(),
3112                        &targets,
3113                        &ctx.universe,
3114                        stop,
3115                        region_follow,
3116                        &ctx.top_groups,
3117                        &mut claimed,
3118                    );
3119                    if let Some(f) = crossing {
3120                        // See the walk-side twin: the post-switch
3121                        // continuation of THIS scope must already land on
3122                        // `f` (earlier parts abruptly complete, or the
3123                        // chain continues exactly at `f`).
3124                        let cont_dead = parts
3125                            .last()
3126                            .map(|p| crate::structure::region_terminates(p, self.results))
3127                            .unwrap_or(false);
3128                        let cont_is_f = reach.contains(&f);
3129                        let cont_via_case_break = self
3130                            .case_arm_ctx
3131                            .last()
3132                            .map(|&(head, ef, pat)| head == entry && pat && ef == Some(f))
3133                            .unwrap_or(false);
3134                        let bindable = (cont_dead || cont_is_f || cont_via_case_break)
3135                            && self.switch_follow_bindable(&sw, cur, &targets, f);
3136                        if crate::dbg_flag!("JCDC_DBG_SWF") {
3137                            eprintln!("SWBIND-SESE cur={} f={} cont_dead={} cont_is_f={} cont_case={} bindable={}",
3138                                cur, f, cont_dead, cont_is_f, cont_via_case_break, bindable);
3139                        }
3140                        if bindable {
3141                            let mut claimed2 = ctx.consumed.clone();
3142                            sw = self.structure_switch(
3143                                cur,
3144                                selector,
3145                                &targets,
3146                                &ctx.universe,
3147                                stop,
3148                                Some(f),
3149                                &ctx.top_groups,
3150                                &mut claimed2,
3151                            );
3152                            claimed = claimed2;
3153                        }
3154                    }
3155                    self.switch_depth -= 1;
3156                    ctx.consumed = claimed;
3157                    parts.push(sw);
3158                    if crate::dbg_flag!("JCDC_DBG_GOTO") {
3159                        eprintln!("SWCONT cur={} follow={:?} in_reach={:?} in_stop={:?} consumed={:?}",
3160                            cur, follow, follow.map(|f| reach.contains(&f)),
3161                            follow.map(|f| stop.contains(&f)), ctx.consumed);
3162                    }
3163                    match follow {
3164                        Some(f) if reach.contains(&f) => {
3165                            cur = f;
3166                            last_via_goto = false;
3167                            continue;
3168                        }
3169                        _ => break,
3170                    }
3171                }
3172                Term::Jsr | Term::Ret => {
3173                    parts.push(Region::Basic { block: cur });
3174                    break;
3175                }
3176            }
3177        }
3178        ctx.depth -= 1;
3179        match parts.len() {
3180            0 => Region::Empty,
3181            1 => parts.into_iter().next().unwrap(),
3182            _ => Region::Seq(parts),
3183        }
3184    }
3185
3186    /// Recover a folded value-diamond (ternary) at a Cond whose two branches
3187    /// are pure single-value pushes merging at `follow`. Mirrors walk's ternary
3188    /// detection so `x = c ? a : b;` is preserved. Returns None when not a
3189    /// pure diamond (the common control-flow case).
3190    fn sese_ternary(
3191        &self,
3192        cur: usize,
3193        taken: usize,
3194        fall: usize,
3195        follow: Option<usize>,
3196        then_r: &Region,
3197        else_r: &Region,
3198    ) -> Option<(Expr, Expr)> {
3199        let merge = follow?;
3200        // Both branches must be a single pure block that falls into `merge`
3201        // leaving exactly one value, and `merge` must be a builder-folded
3202        // diamond merge.
3203        if !self.diamond_merges.contains(&merge) {
3204            return None;
3205        }
3206        let pure_one = |r: &Region, blk: usize| -> Option<Expr> {
3207            match r {
3208                Region::Basic { block } if *block == blk => {
3209                    let res = &self.results[blk];
3210                    if res.stmts.is_empty() && res.out_stack.len() == 1 {
3211                        match &res.term {
3212                            Term::Fallthrough => Some(res.out_stack[0].clone()),
3213                            Term::Goto if self.cfg.blocks[blk].succ == vec![merge] => {
3214                                Some(res.out_stack[0].clone())
3215                            }
3216                            _ => None,
3217                        }
3218                    } else {
3219                        None
3220                    }
3221                }
3222                Region::Empty => {
3223                    // Branch jumps straight to the merge (empty side).
3224                    let res = &self.results[blk];
3225                    if res.stmts.is_empty() && res.out_stack.len() == 1 {
3226                        Some(res.out_stack[0].clone())
3227                    } else {
3228                        None
3229                    }
3230                }
3231                _ => None,
3232            }
3233        };
3234        let tv = pure_one(then_r, taken)?;
3235        let fv = pure_one(else_r, fall)?;
3236        let _ = cur;
3237        Some((tv, fv))
3238    }
3239}