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