Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

panic when an interpreter error gets unintentionally discarded #130885

Merged
merged 2 commits into from
Oct 2, 2024

Conversation

RalfJung
Copy link
Member

One important invariant of Miri is that when an interpreter error is raised (in particular a UB error), those must not be discarded: it's not okay to just check foo().is_err() and then continue executing.

This seems to catch new contributors by surprise fairly regularly, so this PR tries to make it so that if this ever happens, we get a panic rather than a silent missed UB bug. The interpreter error type now contains a "guard" that panics on drop, and that is explicitly passed to mem::forget when an error is deliberately discarded.

Fixes rust-lang/miri#3855

@rustbot
Copy link
Collaborator

rustbot commented Sep 26, 2024

r? @TaKO8Ki

rustbot has assigned @TaKO8Ki.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 26, 2024
@rustbot
Copy link
Collaborator

rustbot commented Sep 26, 2024

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred to MIR optimizations

cc @rust-lang/wg-mir-opt

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The annoying thing is that all the MIR transforms need to use discard_interp_error() instead of ok() now, and if they forget that that's a lurking ICE.

An alternative approach would be to use TLS to track whether the current interpreter has raised an error, and then on the next interpreter step complain if we are still running. But that means more global state, and we'll have to handle the situation of multiple interpreters on the same thread (they should be well-nested, at least) -- so it's not beautiful either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we configure whether InterpErrors are "explosive" in the InterpCx? I'd ideally like to see MIR opts just do InterpCx::new(...).ignore_interp_errors() or something when interfacing with const eval. Or would this still be violating some invariant you'd like to see enforced in MIR opts?

@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member Author

Ah damn, a clippy failure... those are always so painful to fix due to #78717 :/

Copy link
Member

@compiler-errors compiler-errors left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this change in theory, but I do fear that it introduces a new class of somewhat easy to introduce ICEs with much of a "guardrail" to avoid it...

@@ -573,7 +587,7 @@ impl<'a, 'tcx> Collector<'a, 'tcx> {
map: &Map<'tcx>,
) -> Option<Const<'tcx>> {
let ty = place.ty(self.local_decls, self.patch.tcx).ty;
let layout = ecx.layout_of(ty).ok()?;
let layout = ecx.layout_of(ty).discard_interp_error()?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should just call into self.tcx.layout_of -- having a layout_of query return an InterpResult that needs to be defused seems odd, imo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could do that but then we have to pass the ParamEnv by hand. It doesn't mak the code shorter, overall.

There's a lot of code that has TyCtx + ParamEnv but sadly no commonly used type to pack the two leading to a lot of issues like this. :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we configure whether InterpErrors are "explosive" in the InterpCx? I'd ideally like to see MIR opts just do InterpCx::new(...).ignore_interp_errors() or something when interfacing with const eval. Or would this still be violating some invariant you'd like to see enforced in MIR opts?

TrackElem::Field(idx) => self.ecx.project_field(op, idx.as_usize()).ok(),
TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).ok(),
TrackElem::Field(idx) => {
self.ecx.project_field(op, idx.as_usize()).discard_interp_error()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshed: If we can't get rid of all these calls in rustc_mir_transform, perhaps we could at least find a shorter name than discard_interp_error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name must be somewhat scary though or else people might use it in const-eval / miri and not realize what they are doing (and reviewers could easily miss it)...

@@ -557,7 +571,9 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> {
// `SetDiscriminant` may be a no-op if the assigned variant is the untagged variant
// of a niche encoding. If we cannot ensure that we write to the discriminant, do
// nothing.
let Ok(enum_layout) = self.ecx.layout_of(enum_ty) else { return };
let Some(enum_layout) = self.ecx.layout_of(enum_ty).discard_interp_error() else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems really easy to mix up by writing let Ok(_), as noted above.

I'm not certain the best way to avoid this, except for going way overkill and implementing some custom struct (like not a Result but a struct wrapper that makes the Result inaccessible) that implements Try and forces you to deal with the error by either doing ? (via Try) or .discard_interp_error...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's the only type-based alternative I can think of.

@RalfJung
Copy link
Member Author

RalfJung commented Sep 27, 2024

Could we configure whether InterpErrors are "explosive" in the InterpCx?

Unfortunately not, since the code that creates InterpError has no access to the InterpCx.

(Sorry for replying out-of-thread, github as usually has a terrible UI when a review adds a new comment to an existing thread and doesn't make it clear where to reply to a message like this.)

@oli-obk
Copy link
Contributor

oli-obk commented Sep 27, 2024

I think this is just going to be a constant source of ICEs. I can already hear the humming of Matthias's fuzzers.

I don't think TLS for a sanity assertion is a problem and would prefer that solution

@RalfJung
Copy link
Member Author

I don't think TLS for a sanity assertion is a problem and would prefer that solution

What about the "newtyped Result" option discussed above?
To me that sounds more appealing than global state.

@oli-obk
Copy link
Contributor

oli-obk commented Sep 27, 2024

I like it most but assumed from your comments that it was not to your liking.

@RalfJung
Copy link
Member Author

Global state is also not to my liking ;)

@RalfJung
Copy link
Member Author

Argh I wish we had implicit Ok-wrapping, that would make this so much nicer...

@RalfJung RalfJung force-pushed the interp-error-discard branch 2 times, most recently from f07f8d3 to afa5a66 Compare September 29, 2024 09:55
@RalfJung
Copy link
Member Author

Okay, I did that now. :D That made the PR... big...

The new InterpResult type still also has a drop guard to panic on drop. This is to guard against things like

let _ = some_op_that_can_cause_ub();

Note that we panic on drop even if the operation succeeded, so while this does risk ICEs, the ICE should occur always when some code is executed, not just sometimes, making it easier to find with tests. And this has indeed found one discarded UB issue in Miri.

@@ -135,7 +135,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
// neither of which have any effect on our current PRNG.
// See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
let _flags = this.read_scalar(&args[3])?.to_i32();
let _flags = this.read_scalar(&args[3])?.to_i32()?;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the discarded UB issue that was now found -- the final ? was missing.

@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Sep 29, 2024

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung force-pushed the interp-error-discard branch 2 times, most recently from f5c5e1f to fc32fe9 Compare September 29, 2024 21:08
@bors
Copy link
Contributor

bors commented Sep 30, 2024

☔ The latest upstream changes (presumably #131036) made this pull request unmergeable. Please resolve the merge conflicts.

@oli-obk

This comment has been minimized.

@RalfJung

This comment has been minimized.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 1, 2024

r=me with or without any more applications of your last change if you want them.

@RalfJung
Copy link
Member Author

RalfJung commented Oct 1, 2024

@bors r=oli-obk

@bors
Copy link
Contributor

bors commented Oct 1, 2024

📌 Commit c4ce8c1 has been approved by oli-obk

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 1, 2024
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Oct 2, 2024
…li-obk

panic when an interpreter error gets unintentionally discarded

One important invariant of Miri is that when an interpreter error is raised (*in particular* a UB error), those must not be discarded: it's not okay to just check `foo().is_err()` and then continue executing.

This seems to catch new contributors by surprise fairly regularly, so this PR tries to make it so that *if* this ever happens, we get a panic rather than a silent missed UB bug. The interpreter error type now contains a "guard" that panics on drop, and that is explicitly passed to `mem::forget` when an error is deliberately discarded.

Fixes rust-lang/miri#3855
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 2, 2024
…kingjubilee

Rollup of 3 pull requests

Successful merges:

 - rust-lang#130885 (panic when an interpreter error gets unintentionally discarded)
 - rust-lang#131108 (Revert rust-lang#131060 "Drop conditionally applied cargo `-Zon-broken-pipe=kill` flags")
 - rust-lang#131121 (A couple of fixes for dataflow graphviz dumps)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit ea453bb into rust-lang:master Oct 2, 2024
6 checks passed
@rustbot rustbot added this to the 1.83.0 milestone Oct 2, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Oct 2, 2024
Rollup merge of rust-lang#130885 - RalfJung:interp-error-discard, r=oli-obk

panic when an interpreter error gets unintentionally discarded

One important invariant of Miri is that when an interpreter error is raised (*in particular* a UB error), those must not be discarded: it's not okay to just check `foo().is_err()` and then continue executing.

This seems to catch new contributors by surprise fairly regularly, so this PR tries to make it so that *if* this ever happens, we get a panic rather than a silent missed UB bug. The interpreter error type now contains a "guard" that panics on drop, and that is explicitly passed to `mem::forget` when an error is deliberately discarded.

Fixes rust-lang/miri#3855
@bors
Copy link
Contributor

bors commented Oct 2, 2024

⌛ Testing commit c4ce8c1 with merge 2305aad...

@RalfJung RalfJung deleted the interp-error-discard branch October 3, 2024 06:24
tgross35 added a commit to tgross35/rust that referenced this pull request Oct 12, 2024
…=jieyouxu

mark InterpResult as must_use

This was forgotten in rust-lang#130885
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Oct 12, 2024
Rollup merge of rust-lang#131596 - RalfJung:interp-result-must-use, r=jieyouxu

mark InterpResult as must_use

This was forgotten in rust-lang#130885
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make sure interpreter errors are never discarded
7 participants