| author | bors <bors@rust-lang.org> 2026-06-29 18:35:05 UTC |
| committer | bors <bors@rust-lang.org> 2026-06-29 18:35:05 UTC |
| log | 096694416a41840709140eb0fd0ca193d1a3e6ba |
| tree | f199e7547bd2d30d35cad293c7ccca12e2bf9d3a |
| parent | 7dc2c162b9c197aaa76a6f9e7534569537830a01 |
| parent | 3056b8e4ec9fe9498be410e35308f3ba673bba6d |
resolve: Explicit Set for detecting resolution cycles
Instead of using the `borrow_mut` counter of a `RefCell` for a `NameResolution` for detecting cyclic imports during import resolution, we use an explicit recursion stack that keeps track of the current used `NameResolution`s.
Because of the upcoming parallelisation of the import resolution algorithm, the current way cannot used in a parallel context.
r? @petrochenkov3 files changed, 82 insertions(+), 31 deletions(-)
compiler/rustc_resolve/src/ident.rs+15-15| ... | ... | @@ -16,7 +16,7 @@ use tracing::{debug, instrument}; |
| 16 | 16 | |
| 17 | 17 | use crate::diagnostics::{ParamKindInEnumDiscriminant, ParamKindInNonTrivialAnonConst}; |
| 18 | 18 | use crate::hygiene::Macros20NormalizedSyntaxContext; |
| 19 | use crate::imports::{Import, NameResolution}; | |
| 19 | use crate::imports::{Import, NameResolution, cycle_detection}; | |
| 20 | 20 | use crate::late::{ |
| 21 | 21 | ConstantHasGenerics, DiagMetadata, NoConstantGenericsReason, PathSource, Rib, RibKind, |
| 22 | 22 | }; |
| ... | ... | @@ -1148,13 +1148,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 1148 | 1148 | ignore_import: Option<Import<'ra>>, |
| 1149 | 1149 | ) -> Result<Decl<'ra>, ControlFlow<Determinacy, Determinacy>> { |
| 1150 | 1150 | let key = BindingKey::new(ident, ns); |
| 1151 | // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding | |
| 1152 | // doesn't need to be mutable. It will fail when there is a cycle of imports, and without | |
| 1153 | // the exclusive access infinite recursion will crash the compiler with stack overflow. | |
| 1154 | let resolution = &*self | |
| 1155 | .resolution_or_default(module.to_module(), key, orig_ident_span) | |
| 1156 | .try_borrow_mut_unchecked() | |
| 1157 | .map_err(|_| ControlFlow::Continue(Determined))?; | |
| 1151 | let resolution_ref = self.resolution_or_default(module.to_module(), key, orig_ident_span); | |
| 1152 | let resolution = resolution_ref.borrow(); | |
| 1158 | 1153 | |
| 1159 | 1154 | let binding = resolution.non_glob_decl.filter(|b| Some(*b) != ignore_decl); |
| 1160 | 1155 | |
| ... | ... | @@ -1175,6 +1170,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 1175 | 1170 | return if accessible { Ok(binding) } else { Err(ControlFlow::Break(Determined)) }; |
| 1176 | 1171 | } |
| 1177 | 1172 | |
| 1173 | // We need to detect resolution cycles to avoid infinite recursion. The guard ensures | |
| 1174 | // the resolution is removed when this resolve call ends. | |
| 1175 | let _cycle_guard = cycle_detection::enter_cycle_detector(resolution_ref) | |
| 1176 | .map_err(|_| ControlFlow::Continue(Determined))?; | |
| 1177 | ||
| 1178 | 1178 | // Check if one of single imports can still define the name, block if it can. |
| 1179 | 1179 | if self.reborrow().single_import_can_define_name( |
| 1180 | 1180 | &resolution, |
| ... | ... | @@ -1210,13 +1210,8 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 1210 | 1210 | ignore_import: Option<Import<'ra>>, |
| 1211 | 1211 | ) -> Result<Decl<'ra>, ControlFlow<Determinacy, Determinacy>> { |
| 1212 | 1212 | let key = BindingKey::new(ident, ns); |
| 1213 | // `try_borrow_mut` is required to ensure exclusive access, even if the resulting binding | |
| 1214 | // doesn't need to be mutable. It will fail when there is a cycle of imports, and without | |
| 1215 | // the exclusive access infinite recursion will crash the compiler with stack overflow. | |
| 1216 | let resolution = &*self | |
| 1217 | .resolution_or_default(module.to_module(), key, orig_ident_span) | |
| 1218 | .try_borrow_mut_unchecked() | |
| 1219 | .map_err(|_| ControlFlow::Continue(Determined))?; | |
| 1213 | let resolution_ref = self.resolution_or_default(module.to_module(), key, orig_ident_span); | |
| 1214 | let resolution = resolution_ref.borrow(); | |
| 1220 | 1215 | |
| 1221 | 1216 | let binding = resolution.glob_decl.filter(|b| Some(*b) != ignore_decl); |
| 1222 | 1217 | |
| ... | ... | @@ -1231,6 +1226,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 1231 | 1226 | ); |
| 1232 | 1227 | } |
| 1233 | 1228 | |
| 1229 | // We need to detect resolution cycles to avoid infinite recursion. The guard ensures | |
| 1230 | // the resolution is removed when this resolve call ends. | |
| 1231 | let _cycle_guard = cycle_detection::enter_cycle_detector(resolution_ref) | |
| 1232 | .map_err(|_| ControlFlow::Continue(Determined))?; | |
| 1233 | ||
| 1234 | 1234 | // Check if one of single imports can still define the name, |
| 1235 | 1235 | // if it can then our result is not determined and can be invalidated. |
| 1236 | 1236 | if self.reborrow().single_import_can_define_name( |
compiler/rustc_resolve/src/imports.rs+55-1| ... | ... | @@ -32,7 +32,7 @@ use crate::diagnostics::{ |
| 32 | 32 | ConsiderMarkingAsPubCrate, |
| 33 | 33 | }; |
| 34 | 34 | use crate::error_helper::{OnUnknownData, Suggestion}; |
| 35 | use crate::ref_mut::CmCell; | |
| 35 | use crate::ref_mut::{CmCell, CmRefCell}; | |
| 36 | 36 | use crate::{ |
| 37 | 37 | AmbiguityError, BindingKey, CmResolver, Decl, DeclData, DeclKind, Determinacy, Finalize, |
| 38 | 38 | IdentKey, ImportSuggestion, ImportSummary, LocalModule, ModuleOrUniformRoot, ParentScope, |
| ... | ... | @@ -292,6 +292,8 @@ pub(crate) struct NameResolution<'ra> { |
| 292 | 292 | pub orig_ident_span: Span, |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | pub(crate) type NameResolutionRef<'ra> = Interned<'ra, CmRefCell<NameResolution<'ra>>>; | |
| 296 | ||
| 295 | 297 | impl<'ra> NameResolution<'ra> { |
| 296 | 298 | pub(crate) fn new(orig_ident_span: Span) -> Self { |
| 297 | 299 | NameResolution { single_imports: FxIndexSet::default(), orig_ident_span, .. } |
| ... | ... | @@ -320,6 +322,57 @@ impl<'ra> NameResolution<'ra> { |
| 320 | 322 | } |
| 321 | 323 | } |
| 322 | 324 | |
| 325 | // module to keep the TLS private and only accessible through the function `enter_cycle_detector`. | |
| 326 | pub(crate) mod cycle_detection { | |
| 327 | use std::ptr; | |
| 328 | ||
| 329 | use crate::CacheRefCell; | |
| 330 | use crate::imports::NameResolutionRef; | |
| 331 | ||
| 332 | thread_local!( | |
| 333 | /// During import resolution, recursive imports can form cycles. | |
| 334 | /// This set stores the active resolution stack for the current thread. | |
| 335 | /// So it's essentially a recursion stack. | |
| 336 | /// | |
| 337 | /// The key is the interned address of a `RefCell<NameResolution<'ra>>` allocated | |
| 338 | /// in the `Resolver Arenas` (lifetime `'ra`), it is thus stable and allows casting | |
| 339 | /// to a `*const ()` for comparison. This is done because we can't use lifetimes | |
| 340 | /// other than `'static` in thread local storage. | |
| 341 | static ACTIVE_RESOLUTIONS: CacheRefCell<Vec<*const ()>> = Default::default(); | |
| 342 | ); | |
| 343 | ||
| 344 | pub(crate) struct ActiveResolutionGuard { | |
| 345 | key: *const (), | |
| 346 | } | |
| 347 | ||
| 348 | impl Drop for ActiveResolutionGuard { | |
| 349 | fn drop(&mut self) { | |
| 350 | ACTIVE_RESOLUTIONS.with_borrow_mut(|ar| { | |
| 351 | // Only this guard is allowed to remove this key. | |
| 352 | assert!( | |
| 353 | Some(self.key) == ar.pop(), | |
| 354 | "This guard should be the only one removing this key" | |
| 355 | ); | |
| 356 | }); | |
| 357 | } | |
| 358 | } | |
| 359 | ||
| 360 | /// Returns `Err(())` if a cycle is detected, otherwise this returns a | |
| 361 | /// guard that will remove the resolution when dropped. | |
| 362 | pub(crate) fn enter_cycle_detector<'ra>( | |
| 363 | resolution: NameResolutionRef<'ra>, | |
| 364 | ) -> Result<ActiveResolutionGuard, ()> { | |
| 365 | let key = ptr::from_ref(resolution.0).cast::<()>(); | |
| 366 | ACTIVE_RESOLUTIONS.with_borrow_mut(|ar| { | |
| 367 | if ar.contains(&key) { | |
| 368 | return Err(()); | |
| 369 | } | |
| 370 | ar.push(key); | |
| 371 | Ok(ActiveResolutionGuard { key }) | |
| 372 | }) | |
| 373 | } | |
| 374 | } | |
| 375 | ||
| 323 | 376 | /// An error that may be transformed into a diagnostic later. Used to combine multiple unresolved |
| 324 | 377 | /// import errors within the same use tree into a single diagnostic. |
| 325 | 378 | #[derive(Debug, Clone)] |
| ... | ... | @@ -640,6 +693,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 640 | 693 | let (binding, t) = { |
| 641 | 694 | let resolution = &mut *self |
| 642 | 695 | .resolution_or_default(module.to_module(), key, orig_ident_span) |
| 696 | .0 | |
| 643 | 697 | .borrow_mut(self); |
| 644 | 698 | let old_decl = resolution.determined_decl(); |
| 645 | 699 | let old_vis = old_decl.map(|d| d.vis()); |
compiler/rustc_resolve/src/lib.rs+12-15| ... | ... | @@ -77,6 +77,7 @@ use smallvec::{SmallVec, smallvec}; |
| 77 | 77 | use tracing::{debug, instrument}; |
| 78 | 78 | |
| 79 | 79 | use crate::error_helper::OnUnknownData; |
| 80 | use crate::imports::NameResolutionRef; | |
| 80 | 81 | use crate::ref_mut::{CmCell, CmRefCell}; |
| 81 | 82 | |
| 82 | 83 | mod build_reduced_graph; |
| ... | ... | @@ -639,7 +640,7 @@ impl BindingKey { |
| 639 | 640 | } |
| 640 | 641 | } |
| 641 | 642 | |
| 642 | type Resolutions<'ra> = CmRefCell<FxIndexMap<BindingKey, &'ra CmRefCell<NameResolution<'ra>>>>; | |
| 643 | type Resolutions<'ra> = CmRefCell<FxIndexMap<BindingKey, NameResolutionRef<'ra>>>; | |
| 643 | 644 | |
| 644 | 645 | /// One node in the tree of modules. |
| 645 | 646 | /// |
| ... | ... | @@ -1597,11 +1598,10 @@ impl<'ra> ResolverArenas<'ra> { |
| 1597 | 1598 | fn alloc_import(&'ra self, import: ImportData<'ra>) -> Import<'ra> { |
| 1598 | 1599 | Interned::new_unchecked(self.imports.alloc(import)) |
| 1599 | 1600 | } |
| 1600 | fn alloc_name_resolution( | |
| 1601 | &'ra self, | |
| 1602 | orig_ident_span: Span, | |
| 1603 | ) -> &'ra CmRefCell<NameResolution<'ra>> { | |
| 1604 | self.name_resolutions.alloc(CmRefCell::new(NameResolution::new(orig_ident_span))) | |
| 1601 | fn alloc_name_resolution(&'ra self, orig_ident_span: Span) -> NameResolutionRef<'ra> { | |
| 1602 | Interned::new_unchecked( | |
| 1603 | self.name_resolutions.alloc(CmRefCell::new(NameResolution::new(orig_ident_span))), | |
| 1604 | ) | |
| 1605 | 1605 | } |
| 1606 | 1606 | fn alloc_macro_rules_scope(&'ra self, scope: MacroRulesScope<'ra>) -> MacroRulesScopeRef<'ra> { |
| 1607 | 1607 | self.dropless.alloc(CacheCell::new(scope)) |
| ... | ... | @@ -2213,7 +2213,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 2213 | 2213 | module: Module<'ra>, |
| 2214 | 2214 | key: BindingKey, |
| 2215 | 2215 | ) -> Option<Ref<'ra, NameResolution<'ra>>> { |
| 2216 | self.resolutions(module).borrow().get(&key).map(|resolution| resolution.borrow()) | |
| 2216 | self.resolutions(module).borrow().get(&key).map(|resolution| resolution.0.borrow()) | |
| 2217 | 2217 | } |
| 2218 | 2218 | |
| 2219 | 2219 | fn resolution_or_default( |
| ... | ... | @@ -2221,8 +2221,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { |
| 2221 | 2221 | module: Module<'ra>, |
| 2222 | 2222 | key: BindingKey, |
| 2223 | 2223 | orig_ident_span: Span, |
| 2224 | ) -> &'ra CmRefCell<NameResolution<'ra>> { | |
| 2225 | self.resolutions(module) | |
| 2224 | ) -> NameResolutionRef<'ra> { | |
| 2225 | *self | |
| 2226 | .resolutions(module) | |
| 2226 | 2227 | .borrow_mut_unchecked() |
| 2227 | 2228 | .entry(key) |
| 2228 | 2229 | .or_insert_with(|| self.arenas.alloc_name_resolution(orig_ident_span)) |
| ... | ... | @@ -2833,8 +2834,6 @@ type CmResolver<'r, 'ra, 'tcx> = ref_mut::RefOrMut<'r, Resolver<'ra, 'tcx>>; |
| 2833 | 2834 | // parallel name resolution. |
| 2834 | 2835 | use std::cell::{Cell as CacheCell, RefCell as CacheRefCell}; |
| 2835 | 2836 | |
| 2836 | // FIXME: `*_unchecked` methods in the module below should be eliminated in the process | |
| 2837 | // of migration to parallel name resolution. | |
| 2838 | 2837 | mod ref_mut { |
| 2839 | 2838 | use std::cell::{BorrowMutError, Cell, Ref, RefCell, RefMut}; |
| 2840 | 2839 | use std::fmt; |
| ... | ... | @@ -2942,6 +2941,8 @@ mod ref_mut { |
| 2942 | 2941 | } |
| 2943 | 2942 | |
| 2944 | 2943 | #[track_caller] |
| 2944 | // FIXME: this should be eliminated in the process of migration | |
| 2945 | // to parallel name resolution. | |
| 2945 | 2946 | pub(crate) fn borrow_mut_unchecked(&self) -> RefMut<'_, T> { |
| 2946 | 2947 | self.0.borrow_mut() |
| 2947 | 2948 | } |
| ... | ... | @@ -2954,10 +2955,6 @@ mod ref_mut { |
| 2954 | 2955 | self.0.borrow_mut() |
| 2955 | 2956 | } |
| 2956 | 2957 | |
| 2957 | pub(crate) fn try_borrow_mut_unchecked(&self) -> Result<RefMut<'_, T>, BorrowMutError> { | |
| 2958 | self.0.try_borrow_mut() | |
| 2959 | } | |
| 2960 | ||
| 2961 | 2958 | #[track_caller] |
| 2962 | 2959 | pub(crate) fn try_borrow_mut<'ra, 'tcx>( |
| 2963 | 2960 | &self, |