Skip to content

Commit 561107c

Browse files
committed
Ignore overflow when finding auto-trait impls in Rustdoc
In #72936 (comment), it was determined that some unusual code could cause rustc to overflow when evaluating a predicate of the form `T: AutoTrait`. Even if this is a bug, it will still be possible to cause overflow through writing explicit impls of auto traits, just like any other type of impl. In rustdoc, this overflow can happen simply as a result of defining certain types, since we will automatically generate and evaluate auto-trait predicates when generating documentation. For now, we just ignore overflow during selection if it occurs in rustdoc. We should probably come up with a better way to handle this - e.g. rendering some kind of error in the generated documentation. However, this is a very unusual corner case, and this PR is sufficient to unblock landing a Chalk update in PR #72936 This adds additional hacks to `librustc_trait_selection`. The auto-trait-finding code should probably be completely rewritten, but I think this is good enough for the time being.
1 parent 06e4768 commit 561107c

File tree

4 files changed

+119
-16
lines changed

4 files changed

+119
-16
lines changed

src/librustc_trait_selection/traits/auto_trait.rs

+37-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use super::*;
55

66
use crate::infer::region_constraints::{Constraint, RegionConstraintData};
77
use crate::infer::InferCtxt;
8+
use rustc_middle::infer::canonical::OriginalQueryValues;
89
use rustc_middle::ty::fold::TypeFolder;
910
use rustc_middle::ty::{Region, RegionVid};
1011

@@ -88,7 +89,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
8889
let trait_pred = ty::Binder::bind(trait_ref);
8990

9091
let bail_out = tcx.infer_ctxt().enter(|infcx| {
91-
let mut selcx = SelectionContext::with_negative(&infcx, true);
92+
let mut selcx = SelectionContext::with_negative_and_force_overflow(&infcx);
9293
let result = selcx.select(&Obligation::new(
9394
ObligationCause::dummy(),
9495
orig_env,
@@ -186,11 +187,26 @@ impl<'tcx> AutoTraitFinder<'tcx> {
186187
// At this point, we already have all of the bounds we need. FulfillmentContext is used
187188
// to store all of the necessary region/lifetime bounds in the InferContext, as well as
188189
// an additional sanity check.
189-
let mut fulfill = FulfillmentContext::new();
190+
let mut fulfill = FulfillmentContext::new_rustdoc();
190191
fulfill.register_bound(&infcx, full_env, ty, trait_did, ObligationCause::dummy());
191-
fulfill.select_all_or_error(&infcx).unwrap_or_else(|e| {
192-
panic!("Unable to fulfill trait {:?} for '{:?}': {:?}", trait_did, ty, e)
193-
});
192+
if let Err(errs) = fulfill.select_all_or_error(&infcx) {
193+
if errs.iter().all(|err| {
194+
matches!(
195+
err.code,
196+
FulfillmentErrorCode::CodeSelectionError(SelectionError::Overflow)
197+
)
198+
}) {
199+
info!(
200+
"rustdoc: overflow occured when processing auto-trait {:?} for ty {:?}",
201+
trait_did, ty
202+
);
203+
} else {
204+
panic!(
205+
"Failed to fulfill: {:?} {:?} {:?} : errors {:?}",
206+
ty, trait_did, orig_env, errs
207+
);
208+
}
209+
}
194210

195211
let body_id_map: FxHashMap<_, _> = infcx
196212
.inner
@@ -268,9 +284,10 @@ impl AutoTraitFinder<'tcx> {
268284
fresh_preds: &mut FxHashSet<ty::Predicate<'tcx>>,
269285
only_projections: bool,
270286
) -> Option<(ty::ParamEnv<'tcx>, ty::ParamEnv<'tcx>)> {
287+
debug!("evaluate_predicates: trait_did={:?} ty={:?}", trait_did, ty);
271288
let tcx = infcx.tcx;
272289

273-
let mut select = SelectionContext::with_negative(&infcx, true);
290+
let mut select = SelectionContext::with_negative_and_force_overflow(&infcx);
274291

275292
let mut already_visited = FxHashSet::default();
276293
let mut predicates = VecDeque::new();
@@ -290,9 +307,21 @@ impl AutoTraitFinder<'tcx> {
290307
while let Some(pred) = predicates.pop_front() {
291308
infcx.clear_caches();
292309

293-
if !already_visited.insert(pred) {
310+
let mut _orig_values = OriginalQueryValues::default();
311+
// Canonicalize the predicate before inserting it into our map.
312+
// In some cases, we may repeatedly generate a predicate with fresh
313+
// inference variables which is otherwise identical to an existing predicate.
314+
// To avoid infinitely looping, we treat these predicates as equivalent
315+
// for the purposes of determining when to stop.
316+
//
317+
// We check that our computed `ParamEnv` is sufficient using `FulfillmentContext`,
318+
// so we should get an error if we skip over a necessary predicate.
319+
let canonical_pred = infcx.canonicalize_query(&pred, &mut _orig_values);
320+
321+
if !already_visited.insert(canonical_pred) {
294322
continue;
295323
}
324+
debug!("evaluate_predicates: predicate={:?}", pred);
296325

297326
// Call `infcx.resolve_vars_if_possible` to see if we can
298327
// get rid of any inference variables.
@@ -341,7 +370,7 @@ impl AutoTraitFinder<'tcx> {
341370
&Ok(None) => {}
342371
&Err(SelectionError::Unimplemented) => {
343372
if self.is_param_no_infer(pred.skip_binder().trait_ref.substs) {
344-
already_visited.remove(&pred);
373+
already_visited.remove(&canonical_pred);
345374
self.add_user_pred(
346375
&mut user_computed_preds,
347376
ty::PredicateKind::Trait(pred, hir::Constness::NotConst)

src/librustc_trait_selection/traits/fulfill.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ pub struct FulfillmentContext<'tcx> {
7070
// a snapshot (they don't *straddle* a snapshot, so there
7171
// is no trouble there).
7272
usable_in_snapshot: bool,
73+
74+
// See `SelectionContext::force_propagate_overflow`
75+
rustdoc: bool,
7376
}
7477

7578
#[derive(Clone, Debug)]
@@ -93,6 +96,7 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
9396
predicates: ObligationForest::new(),
9497
register_region_obligations: true,
9598
usable_in_snapshot: false,
99+
rustdoc: false,
96100
}
97101
}
98102

@@ -101,6 +105,7 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
101105
predicates: ObligationForest::new(),
102106
register_region_obligations: true,
103107
usable_in_snapshot: true,
108+
rustdoc: false,
104109
}
105110
}
106111

@@ -109,6 +114,16 @@ impl<'a, 'tcx> FulfillmentContext<'tcx> {
109114
predicates: ObligationForest::new(),
110115
register_region_obligations: false,
111116
usable_in_snapshot: false,
117+
rustdoc: false,
118+
}
119+
}
120+
121+
pub fn new_rustdoc() -> FulfillmentContext<'tcx> {
122+
FulfillmentContext {
123+
predicates: ObligationForest::new(),
124+
register_region_obligations: false,
125+
usable_in_snapshot: false,
126+
rustdoc: true,
112127
}
113128
}
114129

@@ -176,7 +191,11 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> {
176191

177192
// FIXME(#20304) -- cache
178193

179-
let mut selcx = SelectionContext::new(infcx);
194+
let mut selcx = if self.rustdoc {
195+
SelectionContext::with_negative_and_force_overflow(infcx)
196+
} else {
197+
SelectionContext::new(infcx)
198+
};
180199
let mut obligations = vec![];
181200
let normalized_ty = project::normalize_projection_type(
182201
&mut selcx,
@@ -229,7 +248,11 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> {
229248
&mut self,
230249
infcx: &InferCtxt<'_, 'tcx>,
231250
) -> Result<(), Vec<FulfillmentError<'tcx>>> {
232-
let mut selcx = SelectionContext::new(infcx);
251+
let mut selcx = if self.rustdoc {
252+
SelectionContext::with_negative_and_force_overflow(infcx)
253+
} else {
254+
SelectionContext::new(infcx)
255+
};
233256
self.select(&mut selcx)
234257
}
235258

src/librustc_trait_selection/traits/select/mod.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ pub struct SelectionContext<'cx, 'tcx> {
8282
/// and a negative impl
8383
allow_negative_impls: bool,
8484

85+
/// If `true`, propagate `OverflowError` instead of calling `report_overflow_error`,
86+
/// regardless of `TraitQueryMode.` This is a hack used by `rustdoc` to avoid
87+
/// aborting the compilation session when overflow occurs.
88+
force_propagate_overflow: bool,
89+
8590
/// The mode that trait queries run in, which informs our error handling
8691
/// policy. In essence, canonicalized queries need their errors propagated
8792
/// rather than immediately reported because we do not have accurate spans.
@@ -174,6 +179,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
174179
intercrate_ambiguity_causes: None,
175180
allow_negative_impls: false,
176181
query_mode: TraitQueryMode::Standard,
182+
force_propagate_overflow: false,
177183
}
178184
}
179185

@@ -185,21 +191,23 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
185191
intercrate_ambiguity_causes: None,
186192
allow_negative_impls: false,
187193
query_mode: TraitQueryMode::Standard,
194+
force_propagate_overflow: false,
188195
}
189196
}
190197

191-
pub fn with_negative(
198+
/// Only for use by rustdoc
199+
pub fn with_negative_and_force_overflow(
192200
infcx: &'cx InferCtxt<'cx, 'tcx>,
193-
allow_negative_impls: bool,
194201
) -> SelectionContext<'cx, 'tcx> {
195-
debug!("with_negative({:?})", allow_negative_impls);
202+
debug!("with_negative_and_force_overflow()");
196203
SelectionContext {
197204
infcx,
198205
freshener: infcx.freshener(),
199206
intercrate: false,
200207
intercrate_ambiguity_causes: None,
201-
allow_negative_impls,
208+
allow_negative_impls: true,
202209
query_mode: TraitQueryMode::Standard,
210+
force_propagate_overflow: true,
203211
}
204212
}
205213

@@ -215,6 +223,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
215223
intercrate_ambiguity_causes: None,
216224
allow_negative_impls: false,
217225
query_mode,
226+
force_propagate_overflow: false,
218227
}
219228
}
220229

@@ -281,7 +290,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
281290
Err(SelectionError::Overflow) => {
282291
// In standard mode, overflow must have been caught and reported
283292
// earlier.
284-
assert!(self.query_mode == TraitQueryMode::Canonical);
293+
assert!(
294+
self.query_mode == TraitQueryMode::Canonical || self.force_propagate_overflow
295+
);
285296
return Err(SelectionError::Overflow);
286297
}
287298
Err(e) => {
@@ -295,7 +306,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
295306

296307
match self.confirm_candidate(obligation, candidate) {
297308
Err(SelectionError::Overflow) => {
298-
assert!(self.query_mode == TraitQueryMode::Canonical);
309+
assert!(
310+
self.query_mode == TraitQueryMode::Canonical || self.force_propagate_overflow
311+
);
299312
Err(SelectionError::Overflow)
300313
}
301314
Err(e) => Err(e),
@@ -908,6 +921,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
908921
if !self.infcx.tcx.sess.recursion_limit().value_within_limit(obligation.recursion_depth) {
909922
match self.query_mode {
910923
TraitQueryMode::Standard => {
924+
if self.force_propagate_overflow {
925+
return Err(OverflowError);
926+
}
911927
self.infcx().report_overflow_error(error_obligation, true);
912928
}
913929
TraitQueryMode::Canonical => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Tests that we don't fail with an overflow error for certain
2+
// strange types
3+
// See https://github.com/rust-lang/rust/pull/72936#issuecomment-643676915
4+
5+
pub trait Interner {
6+
type InternedType;
7+
}
8+
9+
struct RustInterner<'tcx> {
10+
foo: &'tcx ()
11+
}
12+
13+
impl<'tcx> Interner for RustInterner<'tcx> {
14+
type InternedType = Box<TyData<Self>>;
15+
}
16+
17+
enum TyData<I: Interner> {
18+
FnDef(I::InternedType)
19+
}
20+
21+
struct VariableKind<I: Interner>(I::InternedType);
22+
23+
// @has overflow/struct.BoundVarsCollector.html
24+
// @has - '//code' "impl<'tcx> Send for BoundVarsCollector<'tcx>"
25+
pub struct BoundVarsCollector<'tcx> {
26+
val: VariableKind<RustInterner<'tcx>>
27+
}
28+
29+
fn is_send<T: Send>() {}
30+
31+
struct MyInterner<'tcx> {
32+
val: &'tcx ()
33+
}
34+
35+
fn main() {}

0 commit comments

Comments
 (0)