diff --git a/rapx/src/analysis/core/alias_analysis/default/mop.rs b/rapx/src/analysis/core/alias_analysis/default/mop.rs index 47e48d32..f5da0050 100644 --- a/rapx/src/analysis/core/alias_analysis/default/mop.rs +++ b/rapx/src/analysis/core/alias_analysis/default/mop.rs @@ -1,19 +1,176 @@ use rustc_hir::def_id::DefId; use rustc_middle::{ mir::{ - Operand::{self, Constant, Copy, Move}, - SwitchTargets, TerminatorKind, + Operand::{Constant, Copy, Move}, + TerminatorKind, }, ty::{TyKind, TypingEnv}, }; -use std::collections::HashSet; +use std::{ + cell::{Cell, RefCell}, + collections::HashSet, +}; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use crate::analysis::graphs::scc::{SccInfo, SccTree}; use super::{block::Term, graph::*, *}; +// rustc analysis threads may have a relatively small stack; keep recursion limits conservative. +const CHECK_STACK_LIMIT: usize = 96; +const SCC_DFS_STACK_LIMIT: usize = 128; +const SCC_PATH_CACHE_LIMIT: usize = 2048; + +thread_local! { + static CHECK_DEPTH: Cell = Cell::new(0); + static SCC_DFS_DEPTH: Cell = Cell::new(0); + static SCC_PATH_CACHE: RefCell< + FxHashMap, FxHashMap)>> + > = RefCell::new(FxHashMap::default()); +} + +#[derive(Clone, Hash, PartialEq, Eq)] +struct SccPathCacheKey { + def_id: DefId, + scc_enter: usize, + constraints: Vec<(usize, usize)>, +} + +fn constraints_key(constraints: &FxHashMap) -> Vec<(usize, usize)> { + let mut v: Vec<(usize, usize)> = constraints.iter().map(|(k, val)| (*k, *val)).collect(); + v.sort_unstable(); + v +} + +struct DepthLimitGuard { + key: &'static std::thread::LocalKey>, +} + +fn enter_depth_limit( + key: &'static std::thread::LocalKey>, + limit: usize, +) -> Option { + key.with(|d| { + let cur = d.get() + 1; + d.set(cur); + if cur > limit { + d.set(cur - 1); + None + } else { + Some(DepthLimitGuard { key }) + } + }) +} + +impl Drop for DepthLimitGuard { + fn drop(&mut self) { + self.key.with(|d| { + let cur = d.get(); + if cur > 0 { + d.set(cur - 1); + } + }); + } +} + +#[derive(Clone, Hash, PartialEq, Eq)] +struct PathKey { + path: Vec, + constraints: Vec<(usize, usize)>, +} + impl<'tcx> MopGraph<'tcx> { + fn switch_target_for_value( + &self, + targets: &rustc_middle::mir::SwitchTargets, + value: usize, + ) -> usize { + for (v, bb) in targets.iter() { + if v as usize == value { + return bb.as_usize(); + } + } + targets.otherwise().as_usize() + } + + fn unique_otherwise_switch_value( + &self, + discr: &rustc_middle::mir::Operand<'tcx>, + targets: &rustc_middle::mir::SwitchTargets, + ) -> Option { + let tcx = self.tcx; + let local_decls = &tcx.optimized_mir(self.def_id).local_decls; + + let place = match discr { + Copy(p) | Move(p) => Some(*p), + _ => None, + }?; + + let place_ty = place.ty(local_decls, tcx).ty; + let possible_values: Vec = match place_ty.kind() { + TyKind::Bool => vec![0, 1], + TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(), + _ => return None, + }; + + let mut seen = FxHashSet::default(); + for (val, _) in targets.iter() { + seen.insert(val as usize); + } + + let remaining: Vec = possible_values + .into_iter() + .filter(|v| !seen.contains(v)) + .collect(); + + if remaining.len() == 1 { + Some(remaining[0]) + } else { + None + } + } + + fn record_scc_exit_path( + &self, + scc: &SccInfo, + node: usize, + constraints: &FxHashMap, + cur_path: &Vec, + out: &mut Vec<(Vec, FxHashMap)>, + seen_paths: &mut FxHashSet, + ) { + if !scc.exits.iter().any(|e| e.exit == node) { + return; + } + + let key = PathKey { + path: cur_path.clone(), + constraints: constraints_key(constraints), + }; + if seen_paths.insert(key) { + out.push((cur_path.clone(), constraints.clone())); + } + } + + fn possible_switch_values_for_constraint_id(&self, discr_local: usize) -> Option> { + let tcx = self.tcx; + let local_decls = &tcx.optimized_mir(self.def_id).local_decls; + if discr_local >= local_decls.len() { + return None; + } + + let ty = local_decls[rustc_middle::mir::Local::from_usize(discr_local)].ty; + match ty.kind() { + TyKind::Bool => Some(vec![0, 1]), + TyKind::Adt(adt_def, _) if adt_def.is_enum() => { + Some((0..adt_def.variants().len()).collect()) + } + _ => None, + } + } + pub fn split_check( &mut self, bb_idx: usize, @@ -58,6 +215,10 @@ impl<'tcx> MopGraph<'tcx> { fn_map: &mut MopFnAliasMap, recursion_set: &mut HashSet, ) { + let Some(_guard) = enter_depth_limit(&CHECK_DEPTH, CHECK_STACK_LIMIT) else { + // Prevent rustc stack overflow on extremely deep CFG / SCC exploration. + return; + }; self.visit_times += 1; if self.visit_times > VISIT_LIMIT { return; @@ -87,13 +248,18 @@ impl<'tcx> MopGraph<'tcx> { rap_debug!("Searchng paths in scc: {:?}, {:?}", bb_idx, cur_block.scc); let scc_tree = self.sort_scc_tree(&cur_block.scc); rap_debug!("scc_tree: {:?}", scc_tree); - let paths_in_scc = self.find_scc_paths(bb_idx, &scc_tree, &mut FxHashMap::default()); + // Propagate constraints collected so far (top-down) + let inherited_constraints = self.constants.clone(); + let paths_in_scc = self.find_scc_paths(bb_idx, &scc_tree, &inherited_constraints); rap_debug!("Paths found in scc: {:?}", paths_in_scc); let backup_values = self.values.clone(); // duplicate the status when visiteding different paths; let backup_constant = self.constants.clone(); let backup_alias_sets = self.alias_sets.clone(); let backup_recursion_set = recursion_set.clone(); + + // SCC exits are stored on the SCC enter node. + let scc_exits = cur_block.scc.exits.clone(); for raw_path in paths_in_scc { self.alias_sets = backup_alias_sets.clone(); self.values = backup_values.clone(); @@ -103,19 +269,68 @@ impl<'tcx> MopGraph<'tcx> { let path = raw_path.0; let path_constraints = &raw_path.1; rap_debug!("checking path: {:?}", path); - if !path.is_empty() { - for idx in &path[..path.len() - 1] { - self.alias_bb(*idx); - self.alias_bbcall(*idx, fn_map, recursion_set); - } + + // Apply alias transfer for every node in the path (including the exit node). + for idx in &path { + self.alias_bb(*idx); + self.alias_bbcall(*idx, fn_map, recursion_set); } - // The last node is already ouside the scc. - if let Some(&last_node) = path.last() { - if self.blocks[last_node].scc.nodes.is_empty() { - self.check_single_node(last_node, fn_map, recursion_set); - self.handle_nexts(last_node, fn_map, Some(path_constraints), recursion_set); - } else { - // If the exit is an scc, we should handle it like check_scc; + + // The path ends at an SCC-exit node (inside the SCC). We now leave the SCC only via + // recorded SCC exit edges from that node, carrying the collected path constraints. + if let Some(&exit_node) = path.last() { + self.constants.extend(path_constraints); + let mut followed = false; + + // If exit_node is a SwitchInt, only follow SCC-exit edges consistent with the + // current constraint (important for enum parameters). + let mut allowed_targets: Option> = None; + if let Term::Switch(sw) = self.blocks[exit_node].terminator.clone() { + if let TerminatorKind::SwitchInt { discr, targets } = sw.kind { + // Resolve discr local id in the same way as SCC path generation. + let place = match discr { + Copy(p) | Move(p) => Some(self.projection(p)), + _ => None, + }; + if let Some(place) = place { + let discr_local = self + .discriminants + .get(&self.values[place].local) + .cloned() + .unwrap_or(place); + + let mut allowed = FxHashSet::default(); + if let Some(&c) = self.constants.get(&discr_local) { + allowed.insert(self.switch_target_for_value(&targets, c)); + } else { + // No constraint: allow all explicit targets + default. + for (_, bb) in targets.iter() { + allowed.insert(bb.as_usize()); + } + allowed.insert(targets.otherwise().as_usize()); + } + allowed_targets = Some(allowed); + } + } + } + + for e in &scc_exits { + if e.exit != exit_node { + continue; + } + if let Some(allowed) = &allowed_targets { + if !allowed.contains(&e.to) { + continue; + } + } + followed = true; + self.split_check(e.to, fn_map, recursion_set); + } + + // If this SCC path ends at a node that is not recorded as an exit (should be rare), + // fall back to exploring its successors normally. + if !followed { + self.handle_nexts(exit_node, fn_map, Some(path_constraints), recursion_set); } } } @@ -162,14 +377,16 @@ impl<'tcx> MopGraph<'tcx> { let mut sw_target = 0; // Single target let mut path_discr_id = 0; // To avoid analyzing paths that cannot be reached with one enum type. let mut sw_targets = None; // Multiple targets of SwitchInt + let mut sw_otherwise_val: Option = None; match cur_block.terminator { - Term::Switch(switch) => { + Term::Switch(ref switch) => { if let TerminatorKind::SwitchInt { ref discr, ref targets, } = switch.kind { + sw_otherwise_val = self.unique_otherwise_switch_value(discr, targets); match discr { Copy(p) | Move(p) => { let value_idx = self.projection(*p); @@ -214,25 +431,8 @@ impl<'tcx> MopGraph<'tcx> { } } if single_target { - /* Find the target based on the value; - * Since sw_val is a const, only one target is reachable. - * Filed 0 is the value; field 1 is the real target. - */ rap_debug!("targets: {:?}; sw_val = {:?}", targets, sw_val); - for iter in targets.iter() { - if iter.0 as usize == sw_val { - sw_target = iter.1.as_usize(); - break; - } - } - /* No target found, choose the default target. - * The default targets is not included within the iterator. - * We can only obtain the default target based on the last item of all_targets(). - */ - if sw_target == 0 { - let all_target = targets.all_targets(); - sw_target = all_target[all_target.len() - 1].as_usize(); - } + sw_target = self.switch_target_for_value(targets, sw_val); } } } @@ -248,30 +448,49 @@ impl<'tcx> MopGraph<'tcx> { } else { // Other cases in switchInt terminators if let Some(targets) = sw_targets { - for iter in targets.iter() { - if self.visit_times > VISIT_LIMIT { - continue; + if let Some(values) = self.possible_switch_values_for_constraint_id(path_discr_id) { + // Enumerate each possible value explicitly (bool: 0/1, enum: 0..N). + for path_discr_val in values { + if self.visit_times > VISIT_LIMIT { + continue; + } + let next = self.switch_target_for_value(&targets, path_discr_val); + self.split_check_with_cond( + next, + path_discr_id, + path_discr_val, + fn_map, + recursion_set, + ); + } + } else { + // Fallback: explore explicit branches + otherwise. + for iter in targets.iter() { + if self.visit_times > VISIT_LIMIT { + continue; + } + let next = iter.1.as_usize(); + let path_discr_val = iter.0 as usize; + self.split_check_with_cond( + next, + path_discr_id, + path_discr_val, + fn_map, + recursion_set, + ); } - let next = iter.1.as_usize(); - let path_discr_val = iter.0 as usize; + let next_index = targets.otherwise().as_usize(); + // For bool/enum switches, the "otherwise" arm may represent a single concrete + // value (e.g., an enum with 2 variants). Prefer a concrete value when unique. + let path_discr_val = sw_otherwise_val.unwrap_or(usize::MAX); // default/otherwise path self.split_check_with_cond( - next, + next_index, path_discr_id, path_discr_val, fn_map, recursion_set, ); } - let all_targets = targets.all_targets(); - let next_index = all_targets[all_targets.len() - 1].as_usize(); - let path_discr_val = usize::MAX; // to indicate the default path; - self.split_check_with_cond( - next_index, - path_discr_id, - path_discr_val, - fn_map, - recursion_set, - ); } else { for next in cur_block.next { if self.visit_times > VISIT_LIMIT { @@ -313,21 +532,62 @@ impl<'tcx> MopGraph<'tcx> { &mut self, start: usize, scc_tree: &SccTree, - path_constraints: &mut FxHashMap, + initial_constraints: &FxHashMap, ) -> Vec<(Vec, FxHashMap)> { + let key = SccPathCacheKey { + def_id: self.def_id, + scc_enter: scc_tree.scc.enter, + constraints: constraints_key(initial_constraints), + }; + + if let Some(cached) = SCC_PATH_CACHE.with(|c| c.borrow().get(&key).cloned()) { + return cached; + } + let mut all_paths = Vec::new(); - let mut scc_path_set: HashSet> = HashSet::new(); + let mut seen_paths: FxHashSet = FxHashSet::default(); + + // DFS stack path + let mut path = vec![start]; + + // Track nodes on the current *segment* recursion stack (since the last dominator entry). + // This prevents non-dominator cycles inside a segment, but allows revisiting nodes across + // multiple segments separated by returning to the dominator. + let mut segment_stack = FxHashSet::default(); + segment_stack.insert(start); + + // Track nodes visited since entering the dominator on this path. + let mut visited_since_enter = FxHashSet::default(); + visited_since_enter.insert(start); + + // Snapshot of visited nodes at the *start* of the current cycle segment (at dominator). + // Used to decide whether a new cycle (return-to-dominator) introduced new nodes. + let baseline_at_dominator = visited_since_enter.clone(); self.find_scc_paths_inner( start, start, scc_tree, - &mut vec![start], - path_constraints, + &mut path, + initial_constraints.clone(), + segment_stack, + visited_since_enter, + baseline_at_dominator, + None, &mut all_paths, - &mut scc_path_set, + &mut seen_paths, ); + // Paths are deduplicated incrementally via `seen_paths`. + + SCC_PATH_CACHE.with(|c| { + let mut cache = c.borrow_mut(); + if cache.len() >= SCC_PATH_CACHE_LIMIT { + cache.clear(); + } + cache.insert(key, all_paths.clone()); + }); + all_paths } @@ -337,43 +597,75 @@ impl<'tcx> MopGraph<'tcx> { cur: usize, scc_tree: &SccTree, path: &mut Vec, - path_constraints: &mut FxHashMap, + mut path_constraints: FxHashMap, + mut segment_stack: FxHashSet, + visited_since_enter: FxHashSet, + baseline_at_dominator: FxHashSet, + skip_child_enter: Option, paths_in_scc: &mut Vec<(Vec, FxHashMap)>, - scc_path_set: &mut HashSet>, + seen_paths: &mut FxHashSet, ) { - rap_debug!("cur = {}", cur); + let Some(_guard) = enter_depth_limit(&SCC_DFS_DEPTH, SCC_DFS_STACK_LIMIT) else { + return; + }; let scc = &scc_tree.scc; if scc.nodes.is_empty() { - paths_in_scc.push((path.clone(), path_constraints.clone())); + let key = PathKey { + path: path.clone(), + constraints: constraints_key(&path_constraints), + }; + if seen_paths.insert(key) { + paths_in_scc.push((path.clone(), path_constraints)); + } return; } - // FIX ME: a temp complexity control; - if path.len() > 100 || paths_in_scc.len() > 100 { + + // Temporary complexity control to avoid path explosion. + // Use unique-path count to avoid biased truncation from duplicates. + if path.len() > 200 || seen_paths.len() > 4000 { return; } - if !scc.nodes.contains(&cur) && start != cur { - rap_debug!("new path: {:?}", path.clone()); - // we add the next node into the scc path to speedup the traveral outside - // the scc. - paths_in_scc.push((path.clone(), path_constraints.clone())); + + // We do not traverse outside the SCC when generating SCC internal paths. + // Instead, we record paths that end at SCC-exit nodes (nodes with an outgoing edge leaving + // the SCC), and the caller is responsible for resuming traversal outside the SCC. + let cur_in_scc = cur == start || scc.nodes.contains(&cur); + if !cur_in_scc { return; } - if cur == start && path.len() > 1 { - let last_index = path[..path.len() - 1] - .iter() - .rposition(|&node| node == start) - .map(|i| i) - .unwrap_or(0); - let slice = &path[last_index..]; - rap_debug!("path: {:?}", path); - rap_debug!("slice: {:?}", slice); - rap_debug!("set: {:?}", scc_path_set); - if scc_path_set.contains(slice) { - return; - } else { - scc_path_set.insert(slice.to_vec()); + // Incremental cycle validity check: + // When we return to the dominator (start), we keep the cycle only if the *most recent* + // segment between two occurrences of the dominator introduced at least one node that + // was not visited at the beginning of that segment. + let mut baseline_at_dominator = baseline_at_dominator; + let visited_since_enter = visited_since_enter; + + if cur == start { + if path.len() > 1 { + // Find previous occurrence of `start` before the trailing `start`. + let prev_start_pos = path[..path.len() - 1] + .iter() + .rposition(|&node| node == start) + .unwrap_or(0); + // Nodes in the cycle segment exclude both dominator endpoints. + let cycle_nodes = &path[prev_start_pos + 1..path.len() - 1]; + let introduces_new = cycle_nodes + .iter() + .any(|node| !baseline_at_dominator.contains(node)); + if !introduces_new { + return; + } } + // We are at a dominator boundary: reset baseline for the next segment. + baseline_at_dominator = visited_since_enter.clone(); + + // IMPORTANT: reset segment recursion stack at the dominator. + // After completing a cycle back to the dominator, the next segment is allowed to + // traverse previously seen nodes again; only the incremental-cycle check above + // controls whether such repetition is useful. + segment_stack.clear(); + segment_stack.insert(start); } // Clear the constriants if the local is reassigned in the current block; @@ -390,169 +682,432 @@ impl<'tcx> MopGraph<'tcx> { for child_tree in &scc_tree.children { let child_enter = child_tree.scc.enter; if cur == child_enter { - let sub_paths = self.find_scc_paths(child_enter, child_tree, path_constraints); + // When a spliced child-SCC path ends back at the child-enter, we must continue + // exploring the *parent* SCC without immediately re-expanding the same child SCC + // again (otherwise we can loop until the depth guard triggers and drop paths). + if skip_child_enter == Some(child_enter) { + break; + } + + let sub_paths = self.find_scc_paths(child_enter, child_tree, &path_constraints); rap_debug!("paths in sub scc: {}, {:?}", child_enter, sub_paths); + + // Always allow the parent SCC to proceed from `child_enter` without traversing + // into the child SCC (conceptually, an empty child path). This is necessary when + // the child enumeration is truncated/empty and also to allow parent exits at + // `child_enter` (e.g., an edge to a sink `Unreachable` block) to be recorded. + { + let mut nop_path = path.clone(); + self.find_scc_paths_inner( + start, + child_enter, + scc_tree, + &mut nop_path, + path_constraints.clone(), + segment_stack.clone(), + visited_since_enter.clone(), + baseline_at_dominator.clone(), + Some(child_enter), + paths_in_scc, + seen_paths, + ); + } + for (subp, subconst) in sub_paths { + // A single-node sub-path ([child_enter]) makes no progress and would only + // re-trigger child expansion; the no-op continuation above already covers it. + if subp.len() <= 1 { + continue; + } + let mut new_path = path.clone(); new_path.extend(&subp[1..]); - let mut new_const = path_constraints.clone(); - new_const.extend(subconst.iter()); + // `subconst` already starts from `path_constraints` and accumulates changes. + // Use it as the current constraints after splicing. + let new_const = subconst; + + // Update visited set based on spliced nodes. + let mut new_visited = visited_since_enter.clone(); + for node in &new_path { + new_visited.insert(*node); + } + + // Rebuild segment stack from the suffix of the path after the most recent + // dominator occurrence. + let last_start_pos = new_path + .iter() + .rposition(|&node| node == start) + .unwrap_or(0); + let mut new_segment_stack = FxHashSet::default(); + for node in &new_path[last_start_pos..] { + new_segment_stack.insert(*node); + } + + let new_cur = *new_path.last().unwrap(); + let next_skip_child_enter = if new_cur == child_enter { + Some(child_enter) + } else { + None + }; + self.find_scc_paths_inner( start, - *new_path.last().unwrap(), + new_cur, scc_tree, &mut new_path, - &mut new_const, + new_const, + new_segment_stack, + new_visited, + baseline_at_dominator.clone(), + next_skip_child_enter, paths_in_scc, - scc_path_set, + seen_paths, ); } return; } } - let term = &self.terminators[cur].clone(); - + let term = self.terminators[cur].clone(); rap_debug!("term: {:?}", term); + + // Clear constraints when their discriminant locals are redefined by terminators. + // NOTE: `assigned_locals` is populated only from `StatementKind::Assign`, and does not + // include locals assigned by terminators (e.g., `_11 = random()` in MIR is a `Call`). + // If we don't clear these, stale constraints can incorrectly prune SwitchInt branches and + // also amplify path explosion via inconsistent constraint propagation across iterations. + if let TerminatorKind::Call { destination, .. } = &term { + let dest_idx = self.projection(*destination); + let dest_local = self + .discriminants + .get(&self.values[dest_idx].local) + .cloned() + .unwrap_or(dest_idx); + path_constraints.remove(&dest_local); + } + match term { TerminatorKind::SwitchInt { discr, targets } => { - self.handle_switch_int_case( - start, + let otherwise_val = self.unique_otherwise_switch_value(&discr, &targets); + // Resolve discr local id for constraint propagation + let place = match discr { + Copy(p) | Move(p) => Some(self.projection(p)), + _ => None, + }; + + let Some(place) = place else { + return; + }; + + let discr_local = self + .discriminants + .get(&self.values[place].local) + .cloned() + .unwrap_or(place); + + let possible_values = self.possible_switch_values_for_constraint_id(discr_local); + + if let Some(&constant) = path_constraints.get(&discr_local) { + // Existing constraint: only explore the feasible branch. + if constant == usize::MAX { + let next = targets.otherwise().as_usize(); + let next_in_scc = next == start || scc.nodes.contains(&next); + if !next_in_scc { + // Exits the SCC via the default branch. + self.record_scc_exit_path( + scc, + cur, + &path_constraints, + path, + paths_in_scc, + seen_paths, + ); + return; + } + if !segment_stack.contains(&next) || next == start { + let mut new_path = path.clone(); + new_path.push(next); + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + path_constraints, + new_segment_stack, + new_visited, + baseline_at_dominator, + None, + paths_in_scc, + seen_paths, + ); + } + } else { + let mut found = false; + for branch in targets.iter() { + if branch.0 as usize != constant { + continue; + } + found = true; + let next = branch.1.as_usize(); + let next_in_scc = next == start || scc.nodes.contains(&next); + if !next_in_scc { + // Exits the SCC via this constrained branch. + self.record_scc_exit_path( + scc, + cur, + &path_constraints, + path, + paths_in_scc, + seen_paths, + ); + continue; + } + if segment_stack.contains(&next) && next != start { + continue; + } + let mut new_path = path.clone(); + new_path.push(next); + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + path_constraints.clone(), + new_segment_stack, + new_visited, + baseline_at_dominator.clone(), + None, + paths_in_scc, + seen_paths, + ); + } + if !found { + let next = targets.otherwise().as_usize(); + let next_in_scc = next == start || scc.nodes.contains(&next); + if !next_in_scc { + // Exits the SCC via the default branch. + self.record_scc_exit_path( + scc, + cur, + &path_constraints, + path, + paths_in_scc, + seen_paths, + ); + return; + } + if !segment_stack.contains(&next) || next == start { + let mut new_path = path.clone(); + new_path.push(next); + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + path_constraints, + new_segment_stack, + new_visited, + baseline_at_dominator, + None, + paths_in_scc, + seen_paths, + ); + } + } + } + } else { + // No constraint yet: if this is a bool/enum discriminant, enumerate every + // possible value explicitly (not just "iter + otherwise"). + if let Some(values) = possible_values { + for constant in values { + let next = self.switch_target_for_value(&targets, constant); + + let next_in_scc = next == start || scc.nodes.contains(&next); + let mut new_constraints = path_constraints.clone(); + new_constraints.insert(discr_local, constant); + + if !next_in_scc { + self.record_scc_exit_path( + scc, + cur, + &new_constraints, + path, + paths_in_scc, + seen_paths, + ); + continue; + } + if segment_stack.contains(&next) && next != start { + continue; + } + + let mut new_path = path.clone(); + new_path.push(next); + + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + new_constraints, + new_segment_stack, + new_visited, + baseline_at_dominator.clone(), + None, + paths_in_scc, + seen_paths, + ); + } + } else { + // Fallback: explore explicit branches + otherwise. + for branch in targets.iter() { + let constant = branch.0 as usize; + let next = branch.1.as_usize(); + let next_in_scc = next == start || scc.nodes.contains(&next); + let mut new_constraints = path_constraints.clone(); + new_constraints.insert(discr_local, constant); + + if !next_in_scc { + self.record_scc_exit_path( + scc, + cur, + &new_constraints, + path, + paths_in_scc, + seen_paths, + ); + continue; + } + if segment_stack.contains(&next) && next != start { + continue; + } + let mut new_path = path.clone(); + new_path.push(next); + + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + new_constraints, + new_segment_stack, + new_visited, + baseline_at_dominator.clone(), + None, + paths_in_scc, + seen_paths, + ); + } + + let next = targets.otherwise().as_usize(); + let next_in_scc = next == start || scc.nodes.contains(&next); + let mut new_constraints = path_constraints; + new_constraints.insert(discr_local, otherwise_val.unwrap_or(usize::MAX)); + + if !next_in_scc { + self.record_scc_exit_path( + scc, + cur, + &new_constraints, + path, + paths_in_scc, + seen_paths, + ); + return; + } + if !segment_stack.contains(&next) || next == start { + let mut new_path = path.clone(); + new_path.push(next); + + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); + + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); + + self.find_scc_paths_inner( + start, + next, + scc_tree, + &mut new_path, + new_constraints, + new_segment_stack, + new_visited, + baseline_at_dominator, + None, + paths_in_scc, + seen_paths, + ); + } + } + } + } + _ => { + // For non-SwitchInt terminators, reaching an SCC-exit node is enough to record a + // usable path segment. Successors leaving the SCC are not traversed here. + self.record_scc_exit_path( + scc, cur, + &path_constraints, path, - scc_tree, - path_constraints, paths_in_scc, - discr, - targets, - scc_path_set, + seen_paths, ); - } - _ => { for next in self.blocks[cur].next.clone() { - path.push(next); - self.find_scc_paths_inner( - start, - next, - scc_tree, - path, - path_constraints, - paths_in_scc, - scc_path_set, - ); - rap_debug!("pop 1 : {:?}", path.last()); - path.pop(); - } - } - } - } + let next_in_scc = next == start || scc.nodes.contains(&next); + if !next_in_scc { + continue; + } + if segment_stack.contains(&next) && next != start { + continue; + } + let mut new_path = path.clone(); + new_path.push(next); - fn handle_switch_int_case( - &mut self, - start: usize, - _cur: usize, - path: &mut Vec, - scc_tree: &SccTree, - path_constraints: &mut FxHashMap, - paths_in_scc: &mut Vec<(Vec, FxHashMap)>, - discr: &Operand<'tcx>, - targets: &SwitchTargets, - scc_path_set: &mut std::collections::HashSet>, - ) { - let place = match discr { - Copy(p) | Move(p) => Some(self.projection(*p)), - _ => None, - }; + let mut new_segment_stack = segment_stack.clone(); + new_segment_stack.insert(next); - if let Some(place) = place { - let discr_local = self - .discriminants - .get(&self.values[place].local) - .cloned() - .unwrap_or(place); + let mut new_visited = visited_since_enter.clone(); + new_visited.insert(next); - rap_debug!( - "Handle SwitchInt, discr = {:?}, local = {:?}", - discr, - discr_local - ); - if let Some(&constant) = path_constraints.get(&discr_local) { - rap_debug!("constant = {:?}", constant); - rap_debug!("targets = {:?}", targets); - let mut otherwise = true; - // Only the branch matching constant - targets - .iter() - .filter(|t| t.0 as usize == constant) - .for_each(|branch| { - let target = branch.1.as_usize(); - path.push(target); - self.find_scc_paths_inner( - start, - target, - scc_tree, - path, - path_constraints, - paths_in_scc, - scc_path_set, - ); - rap_debug!("pop 2: {:?}", path.last()); - path.pop(); - otherwise = false; - }); - if otherwise { - let target = targets.otherwise().as_usize(); - path.push(target); - path_constraints.insert(discr_local, targets.iter().len()); - self.find_scc_paths_inner( - start, - target, - scc_tree, - path, - path_constraints, - paths_in_scc, - scc_path_set, - ); - path_constraints.remove(&discr_local); - rap_debug!("pop 3: {:?}", path.last()); - path.pop(); - } - } else { - // No restriction, try each branch - for branch in targets.iter() { - let constant = branch.0 as usize; - let target = branch.1.as_usize(); - path.push(target); - path_constraints.insert(discr_local, constant); self.find_scc_paths_inner( start, - target, + next, scc_tree, - path, - path_constraints, + &mut new_path, + path_constraints.clone(), + new_segment_stack, + new_visited, + baseline_at_dominator.clone(), + None, paths_in_scc, - scc_path_set, + seen_paths, ); - path_constraints.remove(&discr_local); - rap_debug!("pop 4: {:?}", path.last()); - path.pop(); } - // Handle default branch - let target = targets.otherwise().as_usize(); - path.push(target); - path_constraints.insert(discr_local, targets.iter().len()); - self.find_scc_paths_inner( - start, - target, - scc_tree, - path, - path_constraints, - paths_in_scc, - scc_path_set, - ); - path_constraints.remove(&discr_local); - rap_debug!("pop 5: {:?}", path.last()); - path.pop(); } } }