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