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