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