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