Skip to content

Commit 2f1ac41

Browse files
authored
Rollup merge of #120828 - nnethercote:fix-stash-steal, r=oli-obk
Fix `ErrorGuaranteed` unsoundness with stash/steal. When you stash an error, the error count is incremented. You can then use the non-zero error count to get an `ErrorGuaranteed`. You can then steal the error, which decrements the error count. You can then cancel the error. Example code: ``` fn unsound(dcx: &DiagCtxt) -> ErrorGuaranteed { let sp = rustc_span::DUMMY_SP; let k = rustc_errors::StashKey::Cycle; dcx.struct_err("bogus").stash(sp, k); // increment error count on stash let guar = dcx.has_errors().unwrap(); // ErrorGuaranteed from error count > 0 let err = dcx.steal_diagnostic(sp, k).unwrap(); // decrement error count on steal err.cancel(); // cancel error guar // ErrorGuaranteed with no error emitted! } ``` This commit fixes the problem in the simplest way: by not counting stashed errors in `DiagCtxt::{err_count,has_errors}`. However, just doing this without any other changes leads to over 40 ui test failures. Mostly because of uninteresting extra errors (many saying "type annotations needed" when type inference fails), and in a few cases, due to delayed bugs causing ICEs when no normal errors are printed. To fix these, this commit adds `DiagCtxt::stashed_err_count`, and uses it in three places alongside `DiagCtxt::{has_errors,err_count}`. It's dodgy to rely on it, because unlike `DiagCtxt::err_count` it can go up and down. But it's needed to preserve existing behaviour, and at least the three places that need it are now obvious. r? oli-obk
2 parents 116efb5 + 7619792 commit 2f1ac41

File tree

5 files changed

+65
-42
lines changed

5 files changed

+65
-42
lines changed

compiler/rustc_errors/src/lib.rs

+27-18
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ struct DiagCtxtInner {
433433
/// The number of non-lint errors that have been emitted, including duplicates.
434434
err_count: usize,
435435

436+
/// The number of stashed errors. Unlike the other counts, this can go up
437+
/// and down, so it doesn't guarantee anything.
438+
stashed_err_count: usize,
439+
436440
/// The error count shown to the user at the end.
437441
deduplicated_err_count: usize,
438442
/// The warning count shown to the user at the end.
@@ -602,6 +606,7 @@ impl DiagCtxt {
602606
flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
603607
lint_err_count: 0,
604608
err_count: 0,
609+
stashed_err_count: 0,
605610
deduplicated_err_count: 0,
606611
deduplicated_warn_count: 0,
607612
has_printed: false,
@@ -658,6 +663,7 @@ impl DiagCtxt {
658663
let mut inner = self.inner.borrow_mut();
659664
inner.lint_err_count = 0;
660665
inner.err_count = 0;
666+
inner.stashed_err_count = 0;
661667
inner.deduplicated_err_count = 0;
662668
inner.deduplicated_warn_count = 0;
663669
inner.has_printed = false;
@@ -679,10 +685,8 @@ impl DiagCtxt {
679685
let key = (span.with_parent(None), key);
680686

681687
if diag.is_error() {
682-
if diag.is_lint.is_some() {
683-
inner.lint_err_count += 1;
684-
} else {
685-
inner.err_count += 1;
688+
if diag.is_lint.is_none() {
689+
inner.stashed_err_count += 1;
686690
}
687691
}
688692

@@ -698,10 +702,8 @@ impl DiagCtxt {
698702
let key = (span.with_parent(None), key);
699703
let diag = inner.stashed_diagnostics.remove(&key)?;
700704
if diag.is_error() {
701-
if diag.is_lint.is_some() {
702-
inner.lint_err_count -= 1;
703-
} else {
704-
inner.err_count -= 1;
705+
if diag.is_lint.is_none() {
706+
inner.stashed_err_count -= 1;
705707
}
706708
}
707709
Some(DiagnosticBuilder::new_diagnostic(self, diag))
@@ -927,13 +929,22 @@ impl DiagCtxt {
927929
self.struct_bug(msg).emit()
928930
}
929931

930-
/// This excludes lint errors and delayed bugs.
932+
/// This excludes lint errors, delayed bugs, and stashed errors.
931933
#[inline]
932934
pub fn err_count(&self) -> usize {
933935
self.inner.borrow().err_count
934936
}
935937

936-
/// This excludes lint errors and delayed bugs.
938+
/// This excludes normal errors, lint errors and delayed bugs. Unless
939+
/// absolutely necessary, avoid using this. It's dubious because stashed
940+
/// errors can later be cancelled, so the presence of a stashed error at
941+
/// some point of time doesn't guarantee anything -- there are no
942+
/// `ErrorGuaranteed`s here.
943+
pub fn stashed_err_count(&self) -> usize {
944+
self.inner.borrow().stashed_err_count
945+
}
946+
947+
/// This excludes lint errors, delayed bugs, and stashed errors.
937948
pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
938949
self.inner.borrow().has_errors().then(|| {
939950
// FIXME(nnethercote) find a way to store an `ErrorGuaranteed`.
@@ -942,8 +953,8 @@ impl DiagCtxt {
942953
})
943954
}
944955

945-
/// This excludes delayed bugs. Unless absolutely necessary, prefer
946-
/// `has_errors` to this method.
956+
/// This excludes delayed bugs and stashed errors. Unless absolutely
957+
/// necessary, prefer `has_errors` to this method.
947958
pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
948959
let inner = self.inner.borrow();
949960
let result = inner.has_errors() || inner.lint_err_count > 0;
@@ -954,8 +965,8 @@ impl DiagCtxt {
954965
})
955966
}
956967

957-
/// Unless absolutely necessary, prefer `has_errors` or
958-
/// `has_errors_or_lint_errors` to this method.
968+
/// This excludes stashed errors. Unless absolutely necessary, prefer
969+
/// `has_errors` or `has_errors_or_lint_errors` to this method.
959970
pub fn has_errors_or_lint_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
960971
let inner = self.inner.borrow();
961972
let result =
@@ -1229,10 +1240,8 @@ impl DiagCtxtInner {
12291240
for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
12301241
// Decrement the count tracking the stash; emitting will increment it.
12311242
if diag.is_error() {
1232-
if diag.is_lint.is_some() {
1233-
self.lint_err_count -= 1;
1234-
} else {
1235-
self.err_count -= 1;
1243+
if diag.is_lint.is_none() {
1244+
self.stashed_err_count -= 1;
12361245
}
12371246
} else {
12381247
// Unless they're forced, don't flush stashed warnings when

compiler/rustc_hir_typeck/src/writeback.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -753,10 +753,14 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
753753
}
754754

755755
fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
756-
match self.fcx.dcx().has_errors() {
757-
Some(e) => e,
758-
None => self
759-
.fcx
756+
if let Some(guar) = self.fcx.dcx().has_errors() {
757+
guar
758+
} else if self.fcx.dcx().stashed_err_count() > 0 {
759+
// Without this case we sometimes get uninteresting and extraneous
760+
// "type annotations needed" errors.
761+
self.fcx.dcx().delayed_bug("error in Resolver")
762+
} else {
763+
self.fcx
760764
.err_ctxt()
761765
.emit_inference_failure_err(
762766
self.fcx.tcx.hir().body_owner_def_id(self.body.id()),
@@ -765,7 +769,7 @@ impl<'cx, 'tcx> Resolver<'cx, 'tcx> {
765769
E0282,
766770
false,
767771
)
768-
.emit(),
772+
.emit()
769773
}
770774
}
771775

compiler/rustc_infer/src/infer/at.rs

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl<'tcx> InferCtxt<'tcx> {
8787
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
8888
tainted_by_errors: self.tainted_by_errors.clone(),
8989
err_count_on_creation: self.err_count_on_creation,
90+
stashed_err_count_on_creation: self.stashed_err_count_on_creation,
9091
universe: self.universe.clone(),
9192
intercrate,
9293
next_trait_solver: self.next_trait_solver,

compiler/rustc_infer/src/infer/mod.rs

+24-19
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,12 @@ pub struct InferCtxt<'tcx> {
306306
// FIXME(matthewjasper) Merge into `tainted_by_errors`
307307
err_count_on_creation: usize,
308308

309+
/// Track how many errors were stashed when this infcx is created.
310+
/// Used for the same purpose as `err_count_on_creation`, even
311+
/// though it's weaker because the count can go up and down.
312+
// FIXME(matthewjasper) Merge into `tainted_by_errors`
313+
stashed_err_count_on_creation: usize,
314+
309315
/// What is the innermost universe we have created? Starts out as
310316
/// `UniverseIndex::root()` but grows from there as we enter
311317
/// universal quantifiers.
@@ -711,6 +717,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
711717
reported_signature_mismatch: Default::default(),
712718
tainted_by_errors: Cell::new(None),
713719
err_count_on_creation: tcx.dcx().err_count(),
720+
stashed_err_count_on_creation: tcx.dcx().stashed_err_count(),
714721
universe: Cell::new(ty::UniverseIndex::ROOT),
715722
intercrate,
716723
next_trait_solver,
@@ -1262,26 +1269,24 @@ impl<'tcx> InferCtxt<'tcx> {
12621269
/// inference variables, regionck errors).
12631270
#[must_use = "this method does not have any side effects"]
12641271
pub fn tainted_by_errors(&self) -> Option<ErrorGuaranteed> {
1265-
debug!(
1266-
"is_tainted_by_errors(err_count={}, err_count_on_creation={}, \
1267-
tainted_by_errors={})",
1268-
self.dcx().err_count(),
1269-
self.err_count_on_creation,
1270-
self.tainted_by_errors.get().is_some()
1271-
);
1272-
1273-
if let Some(e) = self.tainted_by_errors.get() {
1274-
return Some(e);
1275-
}
1276-
1277-
if self.dcx().err_count() > self.err_count_on_creation {
1278-
// errors reported since this infcx was made
1279-
let e = self.dcx().has_errors().unwrap();
1280-
self.set_tainted_by_errors(e);
1281-
return Some(e);
1272+
if let Some(guar) = self.tainted_by_errors.get() {
1273+
Some(guar)
1274+
} else if self.dcx().err_count() > self.err_count_on_creation {
1275+
// Errors reported since this infcx was made.
1276+
let guar = self.dcx().has_errors().unwrap();
1277+
self.set_tainted_by_errors(guar);
1278+
Some(guar)
1279+
} else if self.dcx().stashed_err_count() > self.stashed_err_count_on_creation {
1280+
// Errors stashed since this infcx was made. Not entirely reliable
1281+
// because the count of stashed errors can go down. But without
1282+
// this case we get a moderate number of uninteresting and
1283+
// extraneous "type annotations needed" errors.
1284+
let guar = self.dcx().delayed_bug("tainted_by_errors: stashed bug awaiting emission");
1285+
self.set_tainted_by_errors(guar);
1286+
Some(guar)
1287+
} else {
1288+
None
12821289
}
1283-
1284-
None
12851290
}
12861291

12871292
/// Set the "tainted by errors" flag to true. We call this when we

compiler/rustc_interface/src/passes.rs

+4
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,10 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
778778
// kindck is gone now). -nmatsakis
779779
if let Some(reported) = sess.dcx().has_errors() {
780780
return Err(reported);
781+
} else if sess.dcx().stashed_err_count() > 0 {
782+
// Without this case we sometimes get delayed bug ICEs and I don't
783+
// understand why. -nnethercote
784+
return Err(sess.dcx().delayed_bug("some stashed error is waiting for use"));
781785
}
782786

783787
sess.time("misc_checking_3", || {

0 commit comments

Comments
 (0)