-
Notifications
You must be signed in to change notification settings - Fork 104
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
Prevent panics statically #202
Comments
Here is a discussion on another project about having a no panic policy with some good information: https://github.com/orgs/tpm-rs/discussions/5 I think it could help the zerocopy library if it were interested in some automated CI checks to ensure no panics are present in "tier 1" tool chains. |
Awesome, thanks for that breadcrumb! I'd like to go even further than that, and ensure that panics aren't just optimized out, but never emitted in the first place. Do you know if that technique would work in conjunction with |
I like the idea of using |
I'm starting to work on this. I suspect I'll run out of time to send a PR this week but I hope to send one next week. |
Awesome, thanks! I'll assign it to you. Feel free to comment here if you have any questions. |
It turns out that pub trait MaybePanic { fn maybe_panic(&self); }
pub struct WillPanic;
impl MaybePanic for WillPanic {
fn maybe_panic(&self) { panic!(); }
}
#[redpen::dont_panic]
pub fn will_this_panic<M: MaybePanic>(m: M) {
m.maybe_panic();
}
#[redpen::dont_panic]
pub fn main() {
will_this_panic(WillPanic);
} So we have a few tools at our disposal, all of which are incomplete:
These all have their advantages and disadvantages: Linking-based checksPro: Errors exactly when rustc emits a panic in generated code, which is the cost we care about. Red PenNote: At the moment, Pro: Catches most panics. Clippy lintsWith Rust 1.69 (what I happen to have on my system), the following Clippy lints point to code in I also think there is merit in enabling missing_panics_doc, although at the moment it does not point out anything in Pro: Well-supported tool that Looking for feedbackWe can choose to use any combination of linking checks, Red Pen, and Clippy to minimize panics. I would like input from Here's my take:
Maybe I should enable and address the Clippy lints first, then give Red Pen a try and see how many panics it catches that Clippy doesn't? Then we can evaluate whether Red Pen is worth the effort. |
Holy cow, thank you for such a detailed writeup!!
This sounds like a good plan to me!
Do you think it'd be possible to do this in zerocopy itself? A few thoughts:
My hope is that we can get rid of panics without relying on the optimizer, and so we can run this check with
+1
I was thinking we'd just have this be a dev-dependency and only check it during |
Alright, I'll start working on Clippy lint PRs. We can continue discussing the other options in parallel.
With the
Yes, using the test binary works (I don't know why I didn't think of that initially).
We could use a code coverage tool. If we tag a function as
Sadly, with the latest stable compiler (1.73.0), even an empty function requires // Only compiles with optimization
#[no_panic::no_panic]
#[test]
fn foo() {
}
I suspect yes, but it's not obvious to me. |
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
IIUC estebank/redpen@887ea4f should fix the specific example provided in #202 (comment) |
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
Sorry, I definitely phrased my review comment badly. A few points:
I hope that provides some clarity. Let me know if you have any other questions or thoughts on how best to proceed. |
I spent some time looking into this and AFAICT the compiler doesn't try to optimize more to avoid panics. The cases I looked at were division by a non-constant (contains check-for-zero and branch to panic) and slice indexing. For slice indexing, I basically had to remove all uses of it in favor of higher-level operations (iterators, split_at, etc.), or else |
Ah okay cool that's good to know. |
We got bit by another panic path today, again from - let Some(mut counts) = buf
- .get_mut(..size_of::<ResetCounts>())
- .and_then(Ref::<_, ResetCounts>::new)
- else {
+ let Some(mut counts) = ResetCounts::mut_from_prefix(buf) else { I don't have stats, but I've noticed |
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
This enables Clippy's [`expect_used`](https://rust-lang.github.io/rust-clippy/master/index.html#/expect_used) lint, which errors on calls to `Option::expect` and `Result::{expect, expect_err}`. The intent of enabling this lint is to reduce the number of panics emitted in `zerocopy`'s code (issue google#202).
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
How are you detecting these panics? Do you have static tooling that you're using? We're considering landing #1071 to help reduce panic paths, and it'd be good to have a way to test to confirm that it's actually having the desired effect. |
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
@joshlf This was detected via the linking method, as mentioned. This is how we do it specifically:
This forbids panics in a single output binary. It does not function as designed for anything that isn't fully linked. We also could've had the panic handler produce a linking failure by referencing an undefined symbol, but we determined that the developer experience was much worse than a tool explicitly saying "a panic was introduced in this binary, but it forbids them". This tooling is not public and is highly specific to our bespoke build system.
I'm not entirely sure what you mean by "static tooling" here. It doesn't run the code to do the detection, so it's static in that way. |
Do you have a trick for working backwards from the |
This panic is unreachable. Panicing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
This panic is unreachable. Panicing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
@jswrenn It's highly sensitive to compiler and build changes; it's definitely non-ideal but better than re-introducing panic paths after we managed to remove all of them in a binary. It's not uncommon that the panic is in code you didn't directly touch due to the additional optimizer pressure introduced by the change. |
This panic is unreachable. Panicing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
Given the importance of avoiding panic paths to some of our customers, we should, as a rule, avoid only offering (or relying upon) APIs with panic paths. Related to #202
This panic is unreachable. Panicing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
Related: #1125 |
This panic is unreachable. Panicing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
This panic is unreachable. Panicxing is heavyweight, so we're trying to reduce panics (issue google#202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
This panic is unreachable. Panicxing is heavyweight, so we're trying to reduce panics (issue #202). This PR replaces the panic with a branch that should be lighter-weight (if it is not optimized away entirely).
Redpenestebank/redpen#7 is still open, and I haven't seen much movement in the redpen repository. The last commit message on the branch is:
I'll continue waiting to see if development picks up. If it becomes more actively-maintained, then I'll look at using it in zerocopy, but at the moment it seems like it's likely to be more maintenance work than it is worth. Clippy lintsAs an update of my previous summary, I've enabled every Clippy lint and dug through the results to find every not-yet-enabled lint that points to a panic in
The following sections include every location at which the above lints trip: False positives
Technically panics but not actionable
Panics that are part of a public API
In both cases, these functions return Even though we can't fix these two instances without makin breaking changes, it may be worthwhile to enable Panics that "shouldn't" happen and are hard to removeCode comments indicate these panics should never happen, but it isn't obvious that the optimizer will be able to remove them.
Unlike in #544, there isn't a way for these functions to return if ConclusionIt probably makes sense to enable We could also consider replacing the four |
Issues like #200 demonstrate that the compiler is sometimes not smart enough to optimize out panics that we can prove statically won't happen. It would be good if we could:
See also #325, #1125
Mentoring instructions
if: matrix.crate == 'zerocopy' && matrix.toolchain == 'nightly'
#[redpen::dont_panic]
The text was updated successfully, but these errors were encountered: