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.
This commit is contained in:
parent
6894f435d3
commit
7619792107
@ -429,6 +429,10 @@ struct DiagCtxtInner {
|
|||||||
/// The number of non-lint errors that have been emitted, including duplicates.
|
/// The number of non-lint errors that have been emitted, including duplicates.
|
||||||
err_count: usize,
|
err_count: usize,
|
||||||
|
|
||||||
|
/// The number of stashed errors. Unlike the other counts, this can go up
|
||||||
|
/// and down, so it doesn't guarantee anything.
|
||||||
|
stashed_err_count: usize,
|
||||||
|
|
||||||
/// The error count shown to the user at the end.
|
/// The error count shown to the user at the end.
|
||||||
deduplicated_err_count: usize,
|
deduplicated_err_count: usize,
|
||||||
/// The warning count shown to the user at the end.
|
/// The warning count shown to the user at the end.
|
||||||
@ -598,6 +602,7 @@ pub fn with_emitter(emitter: Box<DynEmitter>) -> Self {
|
|||||||
flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
|
flags: DiagCtxtFlags { can_emit_warnings: true, ..Default::default() },
|
||||||
lint_err_count: 0,
|
lint_err_count: 0,
|
||||||
err_count: 0,
|
err_count: 0,
|
||||||
|
stashed_err_count: 0,
|
||||||
deduplicated_err_count: 0,
|
deduplicated_err_count: 0,
|
||||||
deduplicated_warn_count: 0,
|
deduplicated_warn_count: 0,
|
||||||
has_printed: false,
|
has_printed: false,
|
||||||
@ -654,6 +659,7 @@ pub fn reset_err_count(&self) {
|
|||||||
let mut inner = self.inner.borrow_mut();
|
let mut inner = self.inner.borrow_mut();
|
||||||
inner.lint_err_count = 0;
|
inner.lint_err_count = 0;
|
||||||
inner.err_count = 0;
|
inner.err_count = 0;
|
||||||
|
inner.stashed_err_count = 0;
|
||||||
inner.deduplicated_err_count = 0;
|
inner.deduplicated_err_count = 0;
|
||||||
inner.deduplicated_warn_count = 0;
|
inner.deduplicated_warn_count = 0;
|
||||||
inner.has_printed = false;
|
inner.has_printed = false;
|
||||||
@ -675,10 +681,8 @@ pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: Diagnostic) {
|
|||||||
let key = (span.with_parent(None), key);
|
let key = (span.with_parent(None), key);
|
||||||
|
|
||||||
if diag.is_error() {
|
if diag.is_error() {
|
||||||
if diag.is_lint.is_some() {
|
if diag.is_lint.is_none() {
|
||||||
inner.lint_err_count += 1;
|
inner.stashed_err_count += 1;
|
||||||
} else {
|
|
||||||
inner.err_count += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -694,10 +698,8 @@ pub fn steal_diagnostic(&self, span: Span, key: StashKey) -> Option<DiagnosticBu
|
|||||||
let key = (span.with_parent(None), key);
|
let key = (span.with_parent(None), key);
|
||||||
let diag = inner.stashed_diagnostics.remove(&key)?;
|
let diag = inner.stashed_diagnostics.remove(&key)?;
|
||||||
if diag.is_error() {
|
if diag.is_error() {
|
||||||
if diag.is_lint.is_some() {
|
if diag.is_lint.is_none() {
|
||||||
inner.lint_err_count -= 1;
|
inner.stashed_err_count -= 1;
|
||||||
} else {
|
|
||||||
inner.err_count -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(DiagnosticBuilder::new_diagnostic(self, diag))
|
Some(DiagnosticBuilder::new_diagnostic(self, diag))
|
||||||
@ -922,13 +924,22 @@ pub fn bug(&self, msg: impl Into<DiagnosticMessage>) -> ! {
|
|||||||
self.struct_bug(msg).emit()
|
self.struct_bug(msg).emit()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This excludes lint errors and delayed bugs.
|
/// This excludes lint errors, delayed bugs, and stashed errors.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn err_count(&self) -> usize {
|
pub fn err_count(&self) -> usize {
|
||||||
self.inner.borrow().err_count
|
self.inner.borrow().err_count
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This excludes lint errors and delayed bugs.
|
/// This excludes normal errors, lint errors and delayed bugs. Unless
|
||||||
|
/// absolutely necessary, avoid using this. It's dubious because stashed
|
||||||
|
/// errors can later be cancelled, so the presence of a stashed error at
|
||||||
|
/// some point of time doesn't guarantee anything -- there are no
|
||||||
|
/// `ErrorGuaranteed`s here.
|
||||||
|
pub fn stashed_err_count(&self) -> usize {
|
||||||
|
self.inner.borrow().stashed_err_count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This excludes lint errors, delayed bugs, and stashed errors.
|
||||||
pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
|
pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
|
||||||
self.inner.borrow().has_errors().then(|| {
|
self.inner.borrow().has_errors().then(|| {
|
||||||
// FIXME(nnethercote) find a way to store an `ErrorGuaranteed`.
|
// FIXME(nnethercote) find a way to store an `ErrorGuaranteed`.
|
||||||
@ -937,8 +948,8 @@ pub fn has_errors(&self) -> Option<ErrorGuaranteed> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This excludes delayed bugs. Unless absolutely necessary, prefer
|
/// This excludes delayed bugs and stashed errors. Unless absolutely
|
||||||
/// `has_errors` to this method.
|
/// necessary, prefer `has_errors` to this method.
|
||||||
pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
|
pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
|
||||||
let inner = self.inner.borrow();
|
let inner = self.inner.borrow();
|
||||||
let result = inner.has_errors() || inner.lint_err_count > 0;
|
let result = inner.has_errors() || inner.lint_err_count > 0;
|
||||||
@ -949,8 +960,8 @@ pub fn has_errors_or_lint_errors(&self) -> Option<ErrorGuaranteed> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unless absolutely necessary, prefer `has_errors` or
|
/// This excludes stashed errors. Unless absolutely necessary, prefer
|
||||||
/// `has_errors_or_lint_errors` to this method.
|
/// `has_errors` or `has_errors_or_lint_errors` to this method.
|
||||||
pub fn has_errors_or_lint_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
|
pub fn has_errors_or_lint_errors_or_delayed_bugs(&self) -> Option<ErrorGuaranteed> {
|
||||||
let inner = self.inner.borrow();
|
let inner = self.inner.borrow();
|
||||||
let result =
|
let result =
|
||||||
@ -1224,10 +1235,8 @@ fn emit_stashed_diagnostics(&mut self) {
|
|||||||
for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
|
for (_, diag) in std::mem::take(&mut self.stashed_diagnostics).into_iter() {
|
||||||
// Decrement the count tracking the stash; emitting will increment it.
|
// Decrement the count tracking the stash; emitting will increment it.
|
||||||
if diag.is_error() {
|
if diag.is_error() {
|
||||||
if diag.is_lint.is_some() {
|
if diag.is_lint.is_none() {
|
||||||
self.lint_err_count -= 1;
|
self.stashed_err_count -= 1;
|
||||||
} else {
|
|
||||||
self.err_count -= 1;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Unless they're forced, don't flush stashed warnings when
|
// Unless they're forced, don't flush stashed warnings when
|
||||||
|
@ -753,10 +753,14 @@ fn new(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
|
fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
|
||||||
match self.fcx.dcx().has_errors() {
|
if let Some(guar) = self.fcx.dcx().has_errors() {
|
||||||
Some(e) => e,
|
guar
|
||||||
None => self
|
} else if self.fcx.dcx().stashed_err_count() > 0 {
|
||||||
.fcx
|
// Without this case we sometimes get uninteresting and extraneous
|
||||||
|
// "type annotations needed" errors.
|
||||||
|
self.fcx.dcx().delayed_bug("error in Resolver")
|
||||||
|
} else {
|
||||||
|
self.fcx
|
||||||
.err_ctxt()
|
.err_ctxt()
|
||||||
.emit_inference_failure_err(
|
.emit_inference_failure_err(
|
||||||
self.fcx.tcx.hir().body_owner_def_id(self.body.id()),
|
self.fcx.tcx.hir().body_owner_def_id(self.body.id()),
|
||||||
@ -765,7 +769,7 @@ fn report_error(&self, p: impl Into<ty::GenericArg<'tcx>>) -> ErrorGuaranteed {
|
|||||||
E0282,
|
E0282,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.emit(),
|
.emit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ pub fn fork_with_intercrate(&self, intercrate: bool) -> Self {
|
|||||||
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
|
reported_signature_mismatch: self.reported_signature_mismatch.clone(),
|
||||||
tainted_by_errors: self.tainted_by_errors.clone(),
|
tainted_by_errors: self.tainted_by_errors.clone(),
|
||||||
err_count_on_creation: self.err_count_on_creation,
|
err_count_on_creation: self.err_count_on_creation,
|
||||||
|
stashed_err_count_on_creation: self.stashed_err_count_on_creation,
|
||||||
universe: self.universe.clone(),
|
universe: self.universe.clone(),
|
||||||
intercrate,
|
intercrate,
|
||||||
next_trait_solver: self.next_trait_solver,
|
next_trait_solver: self.next_trait_solver,
|
||||||
|
@ -306,6 +306,12 @@ pub struct InferCtxt<'tcx> {
|
|||||||
// FIXME(matthewjasper) Merge into `tainted_by_errors`
|
// FIXME(matthewjasper) Merge into `tainted_by_errors`
|
||||||
err_count_on_creation: usize,
|
err_count_on_creation: usize,
|
||||||
|
|
||||||
|
/// Track how many errors were stashed when this infcx is created.
|
||||||
|
/// Used for the same purpose as `err_count_on_creation`, even
|
||||||
|
/// though it's weaker because the count can go up and down.
|
||||||
|
// FIXME(matthewjasper) Merge into `tainted_by_errors`
|
||||||
|
stashed_err_count_on_creation: usize,
|
||||||
|
|
||||||
/// What is the innermost universe we have created? Starts out as
|
/// What is the innermost universe we have created? Starts out as
|
||||||
/// `UniverseIndex::root()` but grows from there as we enter
|
/// `UniverseIndex::root()` but grows from there as we enter
|
||||||
/// universal quantifiers.
|
/// universal quantifiers.
|
||||||
@ -711,6 +717,7 @@ pub fn build(&mut self) -> InferCtxt<'tcx> {
|
|||||||
reported_signature_mismatch: Default::default(),
|
reported_signature_mismatch: Default::default(),
|
||||||
tainted_by_errors: Cell::new(None),
|
tainted_by_errors: Cell::new(None),
|
||||||
err_count_on_creation: tcx.dcx().err_count(),
|
err_count_on_creation: tcx.dcx().err_count(),
|
||||||
|
stashed_err_count_on_creation: tcx.dcx().stashed_err_count(),
|
||||||
universe: Cell::new(ty::UniverseIndex::ROOT),
|
universe: Cell::new(ty::UniverseIndex::ROOT),
|
||||||
intercrate,
|
intercrate,
|
||||||
next_trait_solver,
|
next_trait_solver,
|
||||||
@ -1261,27 +1268,25 @@ pub fn fresh_args_for_item(&self, span: Span, def_id: DefId) -> GenericArgsRef<'
|
|||||||
/// inference variables, regionck errors).
|
/// inference variables, regionck errors).
|
||||||
#[must_use = "this method does not have any side effects"]
|
#[must_use = "this method does not have any side effects"]
|
||||||
pub fn tainted_by_errors(&self) -> Option<ErrorGuaranteed> {
|
pub fn tainted_by_errors(&self) -> Option<ErrorGuaranteed> {
|
||||||
debug!(
|
if let Some(guar) = self.tainted_by_errors.get() {
|
||||||
"is_tainted_by_errors(err_count={}, err_count_on_creation={}, \
|
Some(guar)
|
||||||
tainted_by_errors={})",
|
} else if self.dcx().err_count() > self.err_count_on_creation {
|
||||||
self.dcx().err_count(),
|
// Errors reported since this infcx was made.
|
||||||
self.err_count_on_creation,
|
let guar = self.dcx().has_errors().unwrap();
|
||||||
self.tainted_by_errors.get().is_some()
|
self.set_tainted_by_errors(guar);
|
||||||
);
|
Some(guar)
|
||||||
|
} else if self.dcx().stashed_err_count() > self.stashed_err_count_on_creation {
|
||||||
if let Some(e) = self.tainted_by_errors.get() {
|
// Errors stashed since this infcx was made. Not entirely reliable
|
||||||
return Some(e);
|
// because the count of stashed errors can go down. But without
|
||||||
}
|
// this case we get a moderate number of uninteresting and
|
||||||
|
// extraneous "type annotations needed" errors.
|
||||||
if self.dcx().err_count() > self.err_count_on_creation {
|
let guar = self.dcx().delayed_bug("tainted_by_errors: stashed bug awaiting emission");
|
||||||
// errors reported since this infcx was made
|
self.set_tainted_by_errors(guar);
|
||||||
let e = self.dcx().has_errors().unwrap();
|
Some(guar)
|
||||||
self.set_tainted_by_errors(e);
|
} else {
|
||||||
return Some(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the "tainted by errors" flag to true. We call this when we
|
/// Set the "tainted by errors" flag to true. We call this when we
|
||||||
/// observe an error from a prior pass.
|
/// observe an error from a prior pass.
|
||||||
|
@ -778,6 +778,10 @@ fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
|
|||||||
// kindck is gone now). -nmatsakis
|
// kindck is gone now). -nmatsakis
|
||||||
if let Some(reported) = sess.dcx().has_errors() {
|
if let Some(reported) = sess.dcx().has_errors() {
|
||||||
return Err(reported);
|
return Err(reported);
|
||||||
|
} else if sess.dcx().stashed_err_count() > 0 {
|
||||||
|
// Without this case we sometimes get delayed bug ICEs and I don't
|
||||||
|
// understand why. -nnethercote
|
||||||
|
return Err(sess.dcx().delayed_bug("some stashed error is waiting for use"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sess.time("misc_checking_3", || {
|
sess.time("misc_checking_3", || {
|
||||||
|
Loading…
Reference in New Issue
Block a user