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