Skip to content

Conversation

@yilin0518
Copy link
Contributor

  • Add conservative recursion depth limits for CFG/SCC exploration to avoid rustc stack overflows
    Introduce an SCC path cache keyed by (def_id, scc_enter, constraints) with a size cap

  • Deduplicate SCC paths incrementally via PathKey(path + constraint-key) to reduce path explosion

  • Change SCC traversal to record path segments that end at SCC-exit nodes only; resume outside the SCC using exit edges stored on the SCC enter node

  • When leaving an SCC from a SwitchInt exit node, filter followed exit edges by the current discriminant constraint

  • Improve SwitchInt handling:

    • centralize target selection (including otherwise)
    • explicitly enumerate all bool/enum discriminant values
    • infer a unique “otherwise” value when possible; otherwise mark as usize::MAX
  • Fix stale-constraint issues by clearing discriminant constraints when Call terminators assign to the destination local (not covered by statement-based assigned_locals)

  • Add segment-stack + “new nodes since dominator” cycle control and child-SCC splicing with a skip-child-enter guard to prevent immediate re-expansion loops

Copilot AI review requested due to automatic review settings February 3, 2026 10:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Reworks the default alias-analysis SCC traversal and SwitchInt constraint handling to reduce path explosion, prevent stack overflows, and improve constraint correctness during CFG exploration.

Changes:

  • Adds conservative recursion depth guards for CFG/SCC exploration and introduces a thread-local SCC path cache with size limits.
  • Reworks SCC path enumeration to record only SCC-internal path segments that end at SCC-exit nodes, then resumes traversal outside the SCC via recorded exit edges.
  • Centralizes SwitchInt target selection, improves enumeration of bool/enum discriminant values, and clears stale discriminant constraints (including for Call terminators) during SCC path enumeration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

let place_ty = place.ty(local_decls, tcx).ty;
let possible_values: Vec<usize> = match place_ty.kind() {
TyKind::Bool => vec![0, 1],
TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(),
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For enums, treating the set of possible SwitchInt discriminant values as 0..adt_def.variants().len() is not generally correct (fieldless enums can have explicit discriminants / repr(Int) and the MIR SwitchInt values are those actual discriminant integers). This can cause incorrect branch selection and incorrect “unique otherwise” inference.

Derive the possible values from the enum’s real discriminants (e.g., via adt_def.discriminants(tcx) / discriminant_for_variant) and compare against the targets.iter() values without assuming a 0..N encoding.

Suggested change
TyKind::Adt(adt_def, _) if adt_def.is_enum() => (0..adt_def.variants().len()).collect(),
TyKind::Adt(adt_def, _) if adt_def.is_enum() => {
adt_def
.discriminants(tcx)
.map(|(_, discr)| discr.val as usize)
.collect()
}

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +170
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,
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible_switch_values_for_constraint_id assumes enum discriminant values are contiguous 0..variants.len(). For enums with explicit discriminants / repr(Int), MIR SwitchInt targets use the actual discriminant integers, so enumerating 0..N can explore non-existent values and miss real ones.

Prefer enumerating the enum’s true discriminant values (using the type’s AdtDef discriminants) to drive branch exploration.

Copilot uses AI. Check for mistakes.
Comment on lines +629 to 636
// 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;
}

Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_scc_paths / find_scc_paths_inner now intentionally returns paths that end at SCC-exit nodes inside the SCC (and expects the caller to resume traversal via scc.exits). This is a behavioral API change: other callers still assume the last node is outside the SCC (e.g., rapx/src/analysis/safedrop/safedrop.rs:174-182 checks last_node.scc.nodes.is_empty() and otherwise hits TODO).

Either update all call sites to the new contract (including safedrop) or provide a separate helper preserving the old “path includes first outside-SCC node” behavior to avoid silent analysis truncation.

Suggested change
// 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;
}

Copilot uses AI. Check for mistakes.
@@ -87,13 +248,18 @@ impl<'tcx> MopGraph<'tcx> {
rap_debug!("Searchng paths in scc: {:?}, {:?}", bb_idx, cur_block.scc);
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in debug message: "Searchng" → "Searching".

Suggested change
rap_debug!("Searchng paths in scc: {:?}, {:?}", bb_idx, cur_block.scc);
rap_debug!("Searching paths in scc: {:?}, {:?}", bb_idx, cur_block.scc);

Copilot uses AI. Check for mistakes.

// 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() {
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path_constraints encodes the final constraint map for this SCC path (after removals for reassigned locals / calls during path enumeration), but self.constants.extend(path_constraints) can only add/overwrite entries—it can’t remove constraints that were cleared. This means stale discriminant constraints may remain in self.constants and incorrectly prune SwitchInt exit-edge filtering / subsequent traversal.

Fix by applying removals as well (e.g., clear relevant keys in self.constants before extending, or otherwise sync self.constants to path_constraints for keys that were part of the inherited constraint set).

Suggested change
if let Some(&exit_node) = path.last() {
if let Some(&exit_node) = path.last() {
// `path_constraints` encodes the final constraint map for this SCC path (after
// any removals during path enumeration). Clear existing entries so that
// `self.constants` does not retain stale constraints from `backup_constant`.
self.constants.clear();

Copilot uses AI. Check for mistakes.
@hxuhack hxuhack merged commit 1d33ab5 into safer-rust:main Feb 4, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants