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