Skip to content

Commit 44ce74e

Browse files
committed
outlives: add cycle detection for inferred outlives predicates
This avoids a compiler hang, see #102966.
1 parent 4bd3078 commit 44ce74e

File tree

6 files changed

+227
-38
lines changed

6 files changed

+227
-38
lines changed

compiler/rustc_hir_analysis/src/outlives/explicit.rs

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> {
1717
pub(crate) fn explicit_predicates_of(
1818
&mut self,
1919
tcx: TyCtxt<'tcx>,
20+
self_did: DefId,
2021
def_id: DefId,
2122
) -> &ty::EarlyBinder<RequiredPredicates<'tcx>> {
2223
self.map.entry(def_id).or_insert_with(|| {
@@ -35,8 +36,10 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> {
3536
tcx,
3637
ty.into(),
3738
reg,
39+
self_did,
3840
span,
3941
&mut required_predicates,
42+
None,
4043
)
4144
}
4245

@@ -45,8 +48,10 @@ impl<'tcx> ExplicitPredicatesMap<'tcx> {
4548
tcx,
4649
reg1.into(),
4750
reg2,
51+
self_did,
4852
span,
4953
&mut required_predicates,
54+
None,
5055
)
5156
}
5257

compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs

+131-23
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,24 @@ use rustc_span::Span;
88
use super::explicit::ExplicitPredicatesMap;
99
use super::utils::*;
1010

11+
pub(super) type GlobalInferredOutlives<'tcx> =
12+
FxHashMap<DefId, ty::EarlyBinder<RequiredPredicates<'tcx>>>;
13+
1114
/// Infer predicates for the items in the crate.
1215
///
1316
/// `global_inferred_outlives`: this is initially the empty map that
1417
/// was generated by walking the items in the crate. This will
1518
/// now be filled with inferred predicates.
16-
pub(super) fn infer_predicates<'tcx>(
17-
tcx: TyCtxt<'tcx>,
18-
) -> FxHashMap<DefId, ty::EarlyBinder<RequiredPredicates<'tcx>>> {
19-
debug!("infer_predicates");
20-
19+
pub(super) fn infer_predicates<'tcx>(tcx: TyCtxt<'tcx>) -> GlobalInferredOutlives<'tcx> {
2120
let mut explicit_map = ExplicitPredicatesMap::new();
2221

2322
let mut global_inferred_outlives = FxHashMap::default();
2423

2524
// If new predicates were added then we need to re-calculate
2625
// all crates since there could be new implied predicates.
26+
let mut round = 1;
2727
'outer: loop {
28+
debug!("infer_predicates: round {round}");
2829
let mut predicates_added = false;
2930

3031
// Visit all the crates and infer predicates
@@ -50,6 +51,7 @@ pub(super) fn infer_predicates<'tcx>(
5051
let field_span = tcx.def_span(field_def.did);
5152
insert_required_predicates_to_be_wf(
5253
tcx,
54+
adt_def.did(),
5355
field_ty,
5456
field_span,
5557
&global_inferred_outlives,
@@ -72,6 +74,22 @@ pub(super) fn infer_predicates<'tcx>(
7274
global_inferred_outlives.get(&item_did.to_def_id()).map_or(0, |p| p.0.len());
7375
if item_required_predicates.len() > item_predicates_len {
7476
predicates_added = true;
77+
if tracing::enabled!(tracing::Level::DEBUG) {
78+
let def_id = item_did.to_def_id();
79+
use std::collections::BTreeSet;
80+
let global_preds: BTreeSet<_> =
81+
global_inferred_outlives.get(&def_id).map_or_else(Default::default, |e| {
82+
e.0.iter().map(|(pred, _)| pred).collect()
83+
});
84+
let computed_preds: BTreeSet<_> =
85+
item_required_predicates.iter().map(|(pred, _)| pred).collect();
86+
let added = computed_preds.difference(&global_preds).collect::<BTreeSet<_>>();
87+
debug!("global_inferred_outlives grew for {def_id:?}, added: {added:?}");
88+
let removed = global_preds.difference(&computed_preds).collect::<BTreeSet<_>>();
89+
if !removed.is_empty() {
90+
debug!("global_inferred_outlives lost predicates: {removed:?}")
91+
}
92+
}
7593
global_inferred_outlives
7694
.insert(item_did.to_def_id(), ty::EarlyBinder(item_required_predicates));
7795
}
@@ -80,16 +98,18 @@ pub(super) fn infer_predicates<'tcx>(
8098
if !predicates_added {
8199
break 'outer;
82100
}
101+
round += 1;
83102
}
84103

85104
global_inferred_outlives
86105
}
87106

88107
fn insert_required_predicates_to_be_wf<'tcx>(
89108
tcx: TyCtxt<'tcx>,
109+
self_did: DefId,
90110
field_ty: Ty<'tcx>,
91111
field_span: Span,
92-
global_inferred_outlives: &FxHashMap<DefId, ty::EarlyBinder<RequiredPredicates<'tcx>>>,
112+
global_inferred_outlives: &GlobalInferredOutlives<'tcx>,
93113
required_predicates: &mut RequiredPredicates<'tcx>,
94114
explicit_map: &mut ExplicitPredicatesMap<'tcx>,
95115
) {
@@ -109,14 +129,22 @@ fn insert_required_predicates_to_be_wf<'tcx>(
109129
// We also want to calculate potential predicates for the T
110130
ty::Ref(region, rty, _) => {
111131
debug!("Ref");
112-
insert_outlives_predicate(tcx, rty.into(), region, field_span, required_predicates);
132+
insert_outlives_predicate(
133+
tcx,
134+
rty.into(),
135+
region,
136+
self_did,
137+
field_span,
138+
required_predicates,
139+
None,
140+
);
113141
}
114142

115143
// For each Adt (struct/enum/union) type `Foo<'a, T>`, we
116144
// can load the current set of inferred and explicit
117145
// predicates from `global_inferred_outlives` and filter the
118146
// ones that are TypeOutlives.
119-
ty::Adt(def, substs) => {
147+
ty::Adt(adt, substs) => {
120148
// First check the inferred predicates
121149
//
122150
// Example 1:
@@ -136,21 +164,83 @@ fn insert_required_predicates_to_be_wf<'tcx>(
136164
// `['b => 'a, U => T]` and thus get the requirement that `T:
137165
// 'a` holds for `Foo`.
138166
debug!("Adt");
139-
if let Some(unsubstituted_predicates) = global_inferred_outlives.get(&def.did()) {
140-
for (unsubstituted_predicate, &span) in &unsubstituted_predicates.0 {
167+
if let Some(unsubstituted_predicates) = global_inferred_outlives.get(&adt.did()) {
168+
for (unsubstituted_predicate, stack) in &unsubstituted_predicates.0 {
141169
// `unsubstituted_predicate` is `U: 'b` in the
142170
// example above. So apply the substitution to
143171
// get `T: 'a` (or `predicate`):
144172
let predicate = unsubstituted_predicates
145173
.rebind(*unsubstituted_predicate)
146174
.subst(tcx, substs);
147-
insert_outlives_predicate(
148-
tcx,
149-
predicate.0,
150-
predicate.1,
151-
span,
152-
required_predicates,
153-
);
175+
176+
// We must detect cycles in the inference. If we don't, rustc can hang.
177+
// Cycles can be formed by associated types on traits when they are used like so:
178+
//
179+
// ```
180+
// trait Trait<'a> { type Assoc: 'a; }
181+
// struct Node<'node, T: Trait<'node>>(Var<'node, T::Assoc>, Option<T::Assoc>);
182+
// struct RGen<R>(std::marker::PhantomData<R>);
183+
// impl<'a, R: 'a> Trait<'a> for RGen<R> { type Assoc = R; }
184+
// struct Var<'var, R: 'var>(Box<Node<'var, RGen<R>>>);
185+
// ```
186+
//
187+
// Visiting Node, we walk the fields and find a Var. Var has an explicit
188+
// R : 'var.
189+
// Node finds this on its Var field, substitutes through, and gets an inferred
190+
// <T as Trait<'node>>::Assoc: 'node.
191+
// Visiting Var, we walk the fields and find a Node. So Var then picks up
192+
// Node's new inferred predicate (in global_inferred_outlives) and substitutes
193+
// the types it passed to Node ('var for 'node, RGen<R> for T).
194+
// So Var gets
195+
// <RGen<R> as Trait<'var>>::Assoc: 'var
196+
// But Node contains a Var. So Node gets
197+
// <RGen<<T as Trait<'node>>::Assoc> as Trait<'node>>::Assoc 'node
198+
// Var gets
199+
// <RGen<<RGen<R> as Trait<'var>>::Assoc> as Trait<'var>>::Assoc: 'var
200+
// Etc. This goes on forever.
201+
//
202+
// We cut off the cycle formation by tracking in a stack the defs that
203+
// have picked up a substituted predicate each time we produce an edge,
204+
// and don't insert a predicate that is simply a substituted version of
205+
// one we've already seen and added.
206+
//
207+
// Try: RUSTC_LOG=rustc_hir_analysis::outlives=debug \
208+
// rustc +stage1 src/test/ui/typeck/issue-102966.rs 2>&1 \
209+
// | rg '(grew|cycle)'
210+
//
211+
// We do not currently treat a type with an explicit bound as the first
212+
// in the visit stack. So Var here does not appear first in the stack,
213+
// Node does, and each of Node and Var will get a version of
214+
// `<RGen<R> as Trait<'node>>::Assoc: 'node` before the cycle is cut at
215+
// Node. This avoids having a second equivalent bound on Node, and also
216+
// having RGen involved in Node's predicates (which would be silly).
217+
//
218+
// It is not clear whether cyclically-substituted versions of bounds we
219+
// already have are always redundant/unnecessary to add to Self.
220+
// This solution avoids digging into `impl Trait for RGen` to find that it
221+
// unifies with an existing bound but it is really a guess that this
222+
// cyclic substitution cannot add valuable information. There may be
223+
// situations when an error is appropriate.
224+
if stack.iter().any(|&(did, _span)| did == self_did) {
225+
debug!(
226+
"detected cycle in inferred_outlives_predicates,\
227+
for unsubstituted predicate {unsubstituted_predicate:?}:\
228+
{self_did:?} found in {stack:?}"
229+
);
230+
} else {
231+
insert_outlives_predicate(
232+
tcx,
233+
predicate.0,
234+
predicate.1,
235+
// Treat the top-level definition we are currently walking the fields of as the
236+
// type visited in the DefStack. Not the field type.
237+
self_did,
238+
field_span,
239+
required_predicates,
240+
// a copy of this is made for the predicate and (self_did, field_span) is pushed.
241+
Some(stack),
242+
);
243+
}
154244
}
155245
}
156246

@@ -159,7 +249,8 @@ fn insert_required_predicates_to_be_wf<'tcx>(
159249
// let _: () = substs.region_at(0);
160250
check_explicit_predicates(
161251
tcx,
162-
def.did(),
252+
self_did,
253+
adt.did(),
163254
substs,
164255
required_predicates,
165256
explicit_map,
@@ -187,6 +278,7 @@ fn insert_required_predicates_to_be_wf<'tcx>(
187278
ex_trait_ref.with_self_ty(tcx, tcx.types.usize).skip_binder().substs;
188279
check_explicit_predicates(
189280
tcx,
281+
self_did,
190282
ex_trait_ref.skip_binder().def_id,
191283
substs,
192284
required_predicates,
@@ -202,6 +294,7 @@ fn insert_required_predicates_to_be_wf<'tcx>(
202294
debug!("Projection");
203295
check_explicit_predicates(
204296
tcx,
297+
self_did,
205298
tcx.parent(obj.item_def_id),
206299
obj.substs,
207300
required_predicates,
@@ -232,23 +325,28 @@ fn insert_required_predicates_to_be_wf<'tcx>(
232325
/// applying the substitution as above.
233326
fn check_explicit_predicates<'tcx>(
234327
tcx: TyCtxt<'tcx>,
328+
// i.e. Foo
329+
self_did: DefId,
330+
// i.e. Bar
235331
def_id: DefId,
236332
substs: &[GenericArg<'tcx>],
237333
required_predicates: &mut RequiredPredicates<'tcx>,
238334
explicit_map: &mut ExplicitPredicatesMap<'tcx>,
239335
ignored_self_ty: Option<Ty<'tcx>>,
240336
) {
241337
debug!(
242-
"check_explicit_predicates(def_id={:?}, \
338+
"check_explicit_predicates(\
339+
self_did={:?},\
340+
def_id={:?}, \
243341
substs={:?}, \
244342
explicit_map={:?}, \
245343
required_predicates={:?}, \
246344
ignored_self_ty={:?})",
247-
def_id, substs, explicit_map, required_predicates, ignored_self_ty,
345+
self_did, def_id, substs, explicit_map, required_predicates, ignored_self_ty,
248346
);
249-
let explicit_predicates = explicit_map.explicit_predicates_of(tcx, def_id);
347+
let explicit_predicates = explicit_map.explicit_predicates_of(tcx, self_did, def_id);
250348

251-
for (outlives_predicate, &span) in &explicit_predicates.0 {
349+
for (outlives_predicate, stack) in &explicit_predicates.0 {
252350
debug!("outlives_predicate = {:?}", &outlives_predicate);
253351

254352
// Careful: If we are inferring the effects of a `dyn Trait<..>`
@@ -293,8 +391,18 @@ fn check_explicit_predicates<'tcx>(
293391
continue;
294392
}
295393

394+
let &(_foo_did, span) = stack.last().unwrap();
296395
let predicate = explicit_predicates.rebind(*outlives_predicate).subst(tcx, substs);
297396
debug!("predicate = {:?}", &predicate);
298-
insert_outlives_predicate(tcx, predicate.0, predicate.1, span, required_predicates);
397+
insert_outlives_predicate(
398+
tcx,
399+
predicate.0,
400+
predicate.1,
401+
// i.e. Foo, not the field ADT definition.
402+
self_did,
403+
span,
404+
required_predicates,
405+
None,
406+
);
299407
}
300408
}

compiler/rustc_hir_analysis/src/outlives/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,22 @@ fn inferred_outlives_crate(tcx: TyCtxt<'_>, (): ()) -> CratePredicatesMap<'_> {
9898
.iter()
9999
.map(|(&def_id, set)| {
100100
let predicates = &*tcx.arena.alloc_from_iter(set.0.iter().filter_map(
101-
|(ty::OutlivesPredicate(kind1, region2), &span)| {
101+
|(ty::OutlivesPredicate(kind1, region2), stack)| {
102+
let &(_, last_span) = stack.last().unwrap();
102103
match kind1.unpack() {
103104
GenericArgKind::Type(ty1) => Some((
104105
ty::Binder::dummy(ty::PredicateKind::TypeOutlives(
105106
ty::OutlivesPredicate(ty1, *region2),
106107
))
107108
.to_predicate(tcx),
108-
span,
109+
last_span,
109110
)),
110111
GenericArgKind::Lifetime(region1) => Some((
111112
ty::Binder::dummy(ty::PredicateKind::RegionOutlives(
112113
ty::OutlivesPredicate(region1, *region2),
113114
))
114115
.to_predicate(tcx),
115-
span,
116+
last_span,
116117
)),
117118
GenericArgKind::Const(_) => {
118119
// Generic consts don't impose any constraints.

0 commit comments

Comments
 (0)