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