-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add suspicious_open_options lint. #11608
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
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @Alexendoo (or someone else) soon. Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
4cb8261
to
5161394
Compare
Agree, was bitten by a similar bug yesterday, which made me realize that this was also the cause for another transient bug we had before (a test which was generating random data of varying length, and would actually fail if data was not fully overwriting the previous version). Upon close inspection, found 10-ish instances of a missing It currently only checks for the |
I also agree this is a really good lint to have, can't say I wasn't bitten by this 😁. Will take a look soon.
IME, we tend to be careful with external crates and usually we try to either do it in a "crate-agnostic" way (e.g., by adding an attribute that any crate can apply to their items, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This already looks really good and it's a neat lint to have, just a few things.
Also, one potential false positive I can see is that, since it currently assumes it'll find all options in the .open(..)
receiver chain, it will incorrectly lint if the chain is broken up into multiple statements:
let mut opts = OpenOptions::new();
opts.truncate(true);
opts.create(true).open("foo");
I suppose we could fix that by looking for a call to OpenOptions::new()
in the receiver chain and only lint if it's in there (in this example it wouldn't be)
span, | ||
"file opened with `append` and `truncate`", | ||
"file opened with `create`, but `truncate` behavior not defined", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can have a help message here? Something like
if you intended to truncate the file, removing the old file contents, call
.truncate(true)
if you intended to overwrite the old file contents and leave the rest as is, call.truncate(false)
Even better if it has a proper suggestion, but just a simple help message works too. For that we could use span_lint_and_then
to add help messages to the diagnostic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea. I have changed to add a better help message. I did spend some time trying to add a suggestion, but despite spending an hour on it, I could not make it work. cargo test
seems to output the right suggestion, trying to run cargo bless
generates the right stuff in the .fixed
file, but still fails, and so does cargo test
afterwards. I just get a cryptic rustfix failed with exit status: 1
.
Happy to hear your suggestions (ahah), or if you feel like uncommenting the one line and making it work...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestion looked good to me (I think you removed them in the last commit?). I believe the problem with the test is that we can't generally mix warnings that have suggestions with warnings that don't in the same file.
In this case, uitest recognizes that there are warnings with suggestions, so it creates a .fixed
file with those suggestions applied, but the other lines will still emit a warning in the .fixed
file, which it then rejects.
We can fix this by moving the tests with a suggestion to its own file (e.g. open_options_fixable.rs
, or suspicious_open_options.rs
)
// unify it. | ||
fn check_open_options(cx: &LateContext<'_>, settings: &[(OpenOption, Argument)], span: Span) { | ||
// The args passed to these methods, if they have been called | ||
let mut options = FxHashMap::default(); |
There was a problem hiding this 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, using a map instead of the code duplication with locals 👍
6c65918
to
3940e9a
Compare
|
☔ The latest upstream changes (presumably #11694) made this pull request unmergeable. Please resolve the merge conflicts. |
f42ec8e
to
928501e
Compare
Fixed conflicts, and re-added the suggestion (thanks @y21 for your hint, splitting the tests into two files indeed worked). As for also matching |
The easiest approach here would be to simply avoid linting in case of I think this would be totally fine for the initial implementation, and improving on this could be made (by anyone) in followup PRs, though of course you can also try to improve it here if you want. I'm not sure how common it is to build up an One way we could implement this is by having the So we would need to add an Then we also need a catch-all Pseudo Rust codefn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: ...) -> bool {
if let ExprKind::MethodCall(path, receiver, arguments, span) = argument.kind {
// ... old code ...
get_open_options(cx, receiver, options)
} else if let ExprKind::Call(callee, _) = argument.kind
&& let ExprKind::Path(path) = callee.kind
&& let Some(did) = cx.qpath_res(path, callee.hir_id).opt_def_id()
{
match_any_def_paths(cx, did, &[&paths::OPEN_OPTIONS_NEW, &paths::FILE_OPTIONS]).is_some()
} else {
false
}
} We could then use this |
☔ The latest upstream changes (presumably #11823) made this pull request unmergeable. Please resolve the merge conflicts. |
Hi @atwam 👋 , I'm currently going through my old PRs, do you need help with this? |
Hum, I could fixthe conflicts, but I'm afraid your latest suggestions go beyond my knowledge of the lib, and I'm a bit under the water, time-wise, to spend enough time to understand and implement your suggestions. |
Ok, no worries! Maybe what we can do is, you could solve the conflicts and I can submit a PR against your fork with the FP fixed so that the fix gets picked up by this PR? |
5a2d482
to
ebab23e
Compare
A'ight @y21, I've tried fixing stuff... it's not done yet.
So take the current version (which tests fine, but has some commented out code that clippy complained about) as needing... much polishing. |
There are merge commits (commits with multiple parents) in your changes. We have a no merge policy so these commits will need to be removed for this pull request to be merged. You can start a rebase with the following commands:
The following commits are merge commits: |
Checks for the suspicious use of OpenOptions::create() without an explicit OpenOptions::truncate(). create() alone will either create a new file or open an existing file. If the file already exists, it will be overwritten when written to, but the file will not be truncated by default. If less data is written to the file than it already contains, the remainder of the file will remain unchanged, and the end of the file will contain old data. In most cases, one should either use `create_new` to ensure the file is created from scratch, or ensure `truncate` is called so that the truncation behaviour is explicit. `truncate(true)` will ensure the file is entirely overwritten with new data, whereas `truncate(false)` will explicitely keep the default behavior. ```rust use std::fs::OpenOptions; OpenOptions::new().create(true).truncate(true); ```
Also rebase and fix conflicts
- New ineffective_open_options had to be fixed. - Now not raising an issue on missing `truncate` when `append(true)` makes the intent clear. - Try implementing more advanced tests for non-chained operations. Fail
2328d98
to
f09cd88
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Great work! CC @Alexendoo
Thanks! @bors r=y21 |
Add suspicious_open_options lint. changelog: [`suspicious_open_options`]: Checks for the suspicious use of std::fs::OpenOptions::create() without an explicit OpenOptions::truncate(). create() alone will either create a new file or open an existing file. If the file already exists, it will be overwritten when written to, but the file will not be truncated by default. If less data is written to the file than it already contains, the remainder of the file will remain unchanged, and the end of the file will contain old data. In most cases, one should either use `create_new` to ensure the file is created from scratch, or ensure `truncate` is called so that the truncation behaviour is explicit. `truncate(true)` will ensure the file is entirely overwritten with new data, whereas `truncate(false)` will explicitely keep the default behavior. ```rust use std::fs::OpenOptions; OpenOptions::new().create(true).truncate(true); ``` - [x] Followed [lint naming conventions][lint_naming] - [x] Added passing UI tests (including committed `.stderr` file) - [x] `cargo test` passes locally - [x] Executed `cargo dev update_lints` - [x] Added lint documentation - [x] Run `cargo dev fmt`
💔 Test failed - checks-action_test |
Let's see, @bors retry |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
changelog: [
suspicious_open_options
]: Checks for the suspicious use of std::fs::OpenOptions::create() without an explicit OpenOptions::truncate().create() alone will either create a new file or open an existing file. If the file already exists, it will be overwritten when written to, but the file will not be truncated by default. If less data is written to the file than it already contains, the remainder of the file will remain unchanged, and the end of the file will contain old data.
In most cases, one should either use
create_new
to ensure the file is created from scratch, or ensuretruncate
is called so that the truncation behaviour is explicit.truncate(true)
will ensure the file is entirely overwritten with new data, whereastruncate(false)
will explicitely keep the default behavior..stderr
file)cargo test
passes locallycargo dev update_lints
cargo dev fmt