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

Make sure interpreter errors are never discarded #3855

Open
RalfJung opened this issue Sep 1, 2024 · 5 comments
Open

Make sure interpreter errors are never discarded #3855

RalfJung opened this issue Sep 1, 2024 · 5 comments
Labels
A-interpreter Area: affects the core interpreter C-enhancement Category: a PR with an enhancement or an issue tracking an accepted enhancement

Comments

@RalfJung
Copy link
Member

RalfJung commented Sep 1, 2024

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. Would be good to make sure this can never happen. Ideally we'd have some sort of static analysis for this, but I can't think of an easy way to do that (could be an interesting clippy lint). The next best thing is to enforce this dynamically. The problem is that InterpError creation does not have access to the InterpCx. So instead we'd need some thread-local state to indicate "this interpreter is busted, don't continue executing in it... and we'd have to hope that nobody creates two interpreter instances on the same thread... but I can't think of a better way right now.

@RalfJung RalfJung added C-enhancement Category: a PR with an enhancement or an issue tracking an accepted enhancement A-interpreter Area: affects the core interpreter labels Sep 1, 2024
@workingjubilee
Copy link
Member

drop bombs to discourage misuse?

@RalfJung
Copy link
Member Author

Yeah, maybe that works.

@tiif
Copy link
Contributor

tiif commented Sep 11, 2024

I actually encountered this a few times so I'd really appreciate this enhancement, here is how I usually encounter this, hope it would help to produce a clearer diagnostic/ a test if anyone would like to work on this:

This usually happen when I forgot to put a ? for function that returns InterpResult, for example:

        match offset {
            None => fd.read(&fd, communicate, buf, count, dest, this), // the `?` is missing here
            Some(offset) => {
                let Ok(offset) = u64::try_from(offset) else {
                    let einval = this.eval_libc("EINVAL");
                    this.set_last_error(einval)?;
                    this.write_int(-1, dest)?;
                    return Ok(());
                };
                fd.pread(communicate, offset, buf, count, dest, this) // the `?` is missing here
            }
        };

(fd.read and fd.pread return InterpResult)

At this point, the compiler will emit an error and suggestion:

warning: unused `Result` that must be used
   --> src/shims/unix/fd.rs:587:9
    |
587 | /         match offset {
588 | |             None => fd.read(&fd, communicate, buf, count, dest, this),
589 | |             Some(offset) => {
590 | |                 let Ok(offset) = u64::try_from(offset) else {
...   |
597 | |             }
598 | |         };
    | |_________^
    |
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
    |
587 |         let _ = match offset {
    |         +++++++

So if I didn't examine the suggestion carefully, I would just directly apply the suggestion and leads to:

        let _ = match offset { // the change is the `let _ = here`
            None => fd.read(&fd, communicate, buf, count, dest, this),
            Some(offset) => {
                let Ok(offset) = u64::try_from(offset) else {
                    let einval = this.eval_libc("EINVAL");
                    this.set_last_error(einval)?;
                    this.write_int(-1, dest)?;
                    return Ok(());
                };
                fd.pread(communicate, offset, buf, count, dest, this)
            }
        };

After the change, it will compile successfully. IMO this error is pretty dangerous as it can only be caught manually or by certain test, and the test error is not helpful for debugging as it points to different direction ( related discussion in zulip )

The correct fix for the initial code is actually:

        match offset {
            None => fd.read(&fd, communicate, buf, count, dest, this)?, // Added a `? `here
            Some(offset) => {
                let Ok(offset) = u64::try_from(offset) else {
                    let einval = this.eval_libc("EINVAL");
                    this.set_last_error(einval)?;
                    this.write_int(-1, dest)?;
                    return Ok(());
                };
                fd.pread(communicate, offset, buf, count, dest, this)? // Added a `?` here
            }
        };

@RalfJung
Copy link
Member Author

Yeah IMO that's a dangerous suggestion to make.

@CAD97
Copy link

CAD97 commented Sep 14, 2024

FWIW diagnostics and compilation errors in rustc work in a similar fashion, and dropping one without emitting it will ICE ultimately via panic_any. Since this is a programmer error and not user error, panicking is a reasonable out.

Just make sure to check if you're already panicking to avoid a double panic abort.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-interpreter Area: affects the core interpreter C-enhancement Category: a PR with an enhancement or an issue tracking an accepted enhancement
Projects
None yet
Development

No branches or pull requests

4 participants