Skip to content

Commit 7619792

Browse files
committed
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.
1 parent 6894f43 commit 7619792

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
@@ -429,6 +429,10 @@ struct DiagCtxtInner {
429429
/// The number of non-lint errors that have been emitted, including duplicates.
430430
err_count: usize,
431431

432+
/// The number of stashed errors. Unlike the other counts, this can go up
433+
/// and down, so it doesn't guarantee anything.
434+
stashed_err_count: usize,
435+
432436
/// The error count shown to the user at the end.
433437
deduplicated_err_count: usize,
434438
/// The warning count shown to the user at the end.
@@ -598,6 +602,7 @@ impl DiagCtxt {
598602
flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
599603
lint_err_count: 0,
600604
err_count: 0,
605+
stashed_err_count: 0,
601606
deduplicated_err_count: 0,
602607
deduplicated_warn_count: 0,
603608
has_printed: false,
@@ -654,6 +659,7 @@ impl DiagCtxt {
654659
let mut inner = self.inner.borrow_mut();
655660
inner.lint_err_count = 0;
656661
inner.err_count = 0;
662+
inner.stashed_err_count = 0;
657663
inner.deduplicated_err_count = 0;
658664
inner.deduplicated_warn_count = 0;
659665
inner.has_printed = false;
@@ -675,10 +681,8 @@ impl DiagCtxt {
675681
let key = (span.with_parent(None), key);
676682

677683
if diag.is_error() {
678-
if diag.is_lint.is_some() {
679-
inner.lint_err_count += 1;
680-
} else {
681-
inner.err_count += 1;
684+
if diag.is_lint.is_none() {
685+
inner.stashed_err_count += 1;
682686
}
683687
}
684688

@@ -694,10 +698,8 @@ impl DiagCtxt {
694698
let key = (span.with_parent(None), key);
695699
let diag = inner.stashed_diagnostics.remove(&key)?;
696700
if diag.is_error() {
697-
if diag.is_lint.is_some() {
698-
inner.lint_err_count -= 1;
699-
} else {
700-
inner.err_count -= 1;
701+
if diag.is_lint.is_none() {
702+
inner.stashed_err_count -= 1;
701703
}
702704
}
703705
Some(DiagnosticBuilder::new_diagnostic(self, diag))
@@ -922,13 +924,22 @@ impl DiagCtxt {
922924
self.struct_bug(msg).emit()
923925
}
924926

925-
/// This excludes lint errors and delayed bugs.
927+
/// This excludes lint errors, delayed bugs, and stashed errors.
926928
#[inline]
927929
pub fn err_count(&self) -> usize {
928930
self.inner.borrow().err_count
929931
}
930932

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

940-
/// This excludes delayed bugs. Unless absolutely necessary, prefer
941-
/// `has_errors` to this method.
951+
/// This excludes delayed bugs and stashed errors. Unless absolutely
952+
/// necessary, prefer `has_errors` to this method.
942953
pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
943954
let inner = self.inner.borrow();
944955
let result = inner.has_errors() || inner.lint_err_count > 0;
@@ -949,8 +960,8 @@ impl DiagCtxt {
949960
})
950961
}
951962

952-
/// Unless absolutely necessary, prefer `has_errors` or
953-
/// `has_errors_or_lint_errors` to this method.
963+
/// This excludes stashed errors. Unless absolutely necessary, prefer
964+
/// `has_errors` or `has_errors_or_lint_errors` to this method.
954965
pub fn has_errors_or_lint_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
955966
let inner = self.inner.borrow();
956967
let result =
@@ -1224,10 +1235,8 @@ impl DiagCtxtInner {
12241235
for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
12251236
// Decrement the count tracking the stash; emitting will increment it.
12261237
if diag.is_error() {
1227-
if diag.is_lint.is_some() {
1228-
self.lint_err_count -= 1;
1229-
} else {
1230-
self.err_count -= 1;
1238+
if diag.is_lint.is_none() {
1239+
self.stashed_err_count -= 1;
12311240
}
12321241
} else {
12331242
// 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,
@@ -1261,26 +1268,24 @@ impl<'tcx> InferCtxt<'tcx> {
12611268
/// inference variables, regionck errors).
12621269
#[must_use = "this method does not have any side effects"]
12631270
pub fn tainted_by_errors(&self) -> Option<ErrorGuaranteed> {
1264-
debug!(
1265-
"is_tainted_by_errors(err_count={}, err_count_on_creation={}, \
1266-
tainted_by_errors={})",
1267-
self.dcx().err_count(),
1268-
self.err_count_on_creation,
1269-
self.tainted_by_errors.get().is_some()
1270-
);
1271-
1272-
if let Some(e) = self.tainted_by_errors.get() {
1273-
return Some(e);
1274-
}
1275-
1276-
if self.dcx().err_count() > self.err_count_on_creation {
1277-
// errors reported since this infcx was made
1278-
let e = self.dcx().has_errors().unwrap();
1279-
self.set_tainted_by_errors(e);
1280-
return Some(e);
1271+
if let Some(guar) = self.tainted_by_errors.get() {
1272+
Some(guar)
1273+
} else if self.dcx().err_count() > self.err_count_on_creation {
1274+
// Errors reported since this infcx was made.
1275+
let guar = self.dcx().has_errors().unwrap();
1276+
self.set_tainted_by_errors(guar);
1277+
Some(guar)
1278+
} else if self.dcx().stashed_err_count() > self.stashed_err_count_on_creation {
1279+
// Errors stashed since this infcx was made. Not entirely reliable
1280+
// because the count of stashed errors can go down. But without
1281+
// this case we get a moderate number of uninteresting and
1282+
// extraneous "type annotations needed" errors.
1283+
let guar = self.dcx().delayed_bug("tainted_by_errors: stashed bug awaiting emission");
1284+
self.set_tainted_by_errors(guar);
1285+
Some(guar)
1286+
} else {
1287+
None
12811288
}
1282-
1283-
None
12841289
}
12851290

12861291
/// 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)