Add suspicious_open_options lint.
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); ```
This commit is contained in:
parent
692f53fe8f
commit
6c201db005
9 changed files with 132 additions and 102 deletions
|
@ -5606,6 +5606,7 @@ Released 2018-09-13
|
|||
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
|
||||
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
|
||||
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
|
||||
[`suspicious_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_open_options
|
||||
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
|
||||
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
|
||||
[`suspicious_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_to_owned
|
||||
|
|
|
@ -439,6 +439,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::methods::STR_SPLIT_AT_NEWLINE_INFO,
|
||||
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,
|
||||
crate::methods::SUSPICIOUS_MAP_INFO,
|
||||
crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO,
|
||||
crate::methods::SUSPICIOUS_SPLITN_INFO,
|
||||
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
|
||||
crate::methods::TYPE_ID_ON_BOX_INFO,
|
||||
|
|
|
@ -2827,6 +2827,37 @@ declare_clippy_lint! {
|
|||
"nonsensical combination of options for opening a file"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for the suspicious use of OpenOptions::create()
|
||||
/// without an explicit OpenOptions::truncate().
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// 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.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use std::fs::OpenOptions;
|
||||
///
|
||||
/// OpenOptions::new().create(true).truncate(true);
|
||||
/// ```
|
||||
#[clippy::version = "1.73.0"]
|
||||
pub SUSPICIOUS_OPEN_OPTIONS,
|
||||
suspicious,
|
||||
"suspicious combination of options for opening a file"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
///* Checks for [push](https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.push)
|
||||
|
@ -4033,6 +4064,7 @@ impl_lint_pass!(Methods => [
|
|||
MAP_ERR_IGNORE,
|
||||
MUT_MUTEX_LOCK,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
SUSPICIOUS_OPEN_OPTIONS,
|
||||
PATH_BUF_PUSH_OVERWRITE,
|
||||
RANGE_ZIP_WITH_LEN,
|
||||
REPEAT_ONCE,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use rustc_data_structures::fx::FxHashMap;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_ast::ast::LitKind;
|
||||
|
@ -6,7 +8,7 @@ use rustc_lint::LateContext;
|
|||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
use super::NONSENSICAL_OPEN_OPTIONS;
|
||||
use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS};
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) {
|
||||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
|
||||
|
@ -19,20 +21,32 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, recv: &'tcx
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
enum Argument {
|
||||
True,
|
||||
False,
|
||||
Set(bool),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
enum OpenOption {
|
||||
Write,
|
||||
Append,
|
||||
Create,
|
||||
CreateNew,
|
||||
Read,
|
||||
Truncate,
|
||||
Create,
|
||||
Append,
|
||||
Write,
|
||||
}
|
||||
impl std::fmt::Display for OpenOption {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
OpenOption::Append => write!(f, "append"),
|
||||
OpenOption::Create => write!(f, "create"),
|
||||
OpenOption::CreateNew => write!(f, "create_new"),
|
||||
OpenOption::Read => write!(f, "read"),
|
||||
OpenOption::Truncate => write!(f, "truncate"),
|
||||
OpenOption::Write => write!(f, "write"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec<(OpenOption, Argument)>) {
|
||||
|
@ -48,7 +62,7 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
|
|||
..
|
||||
} = span
|
||||
{
|
||||
if *lit { Argument::True } else { Argument::False }
|
||||
Argument::Set(*lit)
|
||||
} else {
|
||||
// The function is called with a literal which is not a boolean literal.
|
||||
// This is theoretically possible, but not very likely.
|
||||
|
@ -62,6 +76,9 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
|
|||
"create" => {
|
||||
options.push((OpenOption::Create, argument_option));
|
||||
},
|
||||
"create_new" => {
|
||||
options.push((OpenOption::CreateNew, argument_option));
|
||||
},
|
||||
"append" => {
|
||||
options.push((OpenOption::Append, argument_option));
|
||||
},
|
||||
|
@ -82,97 +99,52 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec
|
|||
}
|
||||
}
|
||||
|
||||
fn check_open_options(cx: &LateContext<'_>, options: &[(OpenOption, Argument)], span: Span) {
|
||||
let (mut create, mut append, mut truncate, mut read, mut write) = (false, false, false, false, false);
|
||||
let (mut create_arg, mut append_arg, mut truncate_arg, mut read_arg, mut write_arg) =
|
||||
(false, false, false, false, false);
|
||||
// This code is almost duplicated (oh, the irony), but I haven't found a way to
|
||||
// unify it.
|
||||
|
||||
for option in options {
|
||||
match *option {
|
||||
(OpenOption::Create, arg) => {
|
||||
if create {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"the method `create` is called more than once",
|
||||
);
|
||||
} else {
|
||||
create = true;
|
||||
}
|
||||
create_arg = create_arg || (arg == Argument::True);
|
||||
},
|
||||
(OpenOption::Append, arg) => {
|
||||
if append {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"the method `append` is called more than once",
|
||||
);
|
||||
} else {
|
||||
append = true;
|
||||
}
|
||||
append_arg = append_arg || (arg == Argument::True);
|
||||
},
|
||||
(OpenOption::Truncate, arg) => {
|
||||
if truncate {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"the method `truncate` is called more than once",
|
||||
);
|
||||
} else {
|
||||
truncate = true;
|
||||
}
|
||||
truncate_arg = truncate_arg || (arg == Argument::True);
|
||||
},
|
||||
(OpenOption::Read, arg) => {
|
||||
if read {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"the method `read` is called more than once",
|
||||
);
|
||||
} else {
|
||||
read = true;
|
||||
}
|
||||
read_arg = read_arg || (arg == Argument::True);
|
||||
},
|
||||
(OpenOption::Write, arg) => {
|
||||
if write {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"the method `write` is called more than once",
|
||||
);
|
||||
} else {
|
||||
write = true;
|
||||
}
|
||||
write_arg = write_arg || (arg == Argument::True);
|
||||
},
|
||||
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();
|
||||
for (option, arg) in settings {
|
||||
if options.insert(option.clone(), arg.clone()).is_some() {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
&format!("the method `{}` is called more than once", &option),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if read && truncate && read_arg && truncate_arg && !(write && write_arg) {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"file opened with `truncate` and `read`",
|
||||
);
|
||||
if let (Some(Argument::Set(true)), Some(Argument::Set(true))) =
|
||||
(options.get(&OpenOption::Read), options.get(&OpenOption::Truncate))
|
||||
{
|
||||
if options.get(&OpenOption::Write).unwrap_or(&Argument::Set(false)) == &Argument::Set(false) {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"file opened with `truncate` and `read`",
|
||||
);
|
||||
}
|
||||
}
|
||||
if append && truncate && append_arg && truncate_arg {
|
||||
|
||||
if let (Some(Argument::Set(true)), Some(Argument::Set(true))) =
|
||||
(options.get(&OpenOption::Append), options.get(&OpenOption::Truncate))
|
||||
{
|
||||
if options.get(&OpenOption::Write).unwrap_or(&Argument::Set(false)) == &Argument::Set(false) {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
span,
|
||||
"file opened with `append` and `truncate`",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(Argument::Set(true)), None) = (options.get(&OpenOption::Create), options.get(&OpenOption::Truncate)) {
|
||||
span_lint(
|
||||
cx,
|
||||
NONSENSICAL_OPEN_OPTIONS,
|
||||
SUSPICIOUS_OPEN_OPTIONS,
|
||||
span,
|
||||
"file opened with `append` and `truncate`",
|
||||
"file opened with `create`, but `truncate` behavior not defined",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::fs::OpenOptions;
|
|||
|
||||
#[allow(unused_must_use)]
|
||||
#[warn(clippy::nonsensical_open_options)]
|
||||
#[warn(clippy::suspicious_open_options)]
|
||||
fn main() {
|
||||
OpenOptions::new().read(true).truncate(true).open("foo.txt");
|
||||
//~^ ERROR: file opened with `truncate` and `read`
|
||||
|
@ -19,4 +20,6 @@ fn main() {
|
|||
//~^ ERROR: the method `append` is called more than once
|
||||
OpenOptions::new().truncate(true).truncate(false).open("foo.txt");
|
||||
//~^ ERROR: the method `truncate` is called more than once
|
||||
OpenOptions::new().create(true).open("foo.txt");
|
||||
//~^ ERROR: file opened with `create`, but `truncate` behavior not defined
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: file opened with `truncate` and `read`
|
||||
--> $DIR/open_options.rs:6:5
|
||||
--> $DIR/open_options.rs:7:5
|
||||
|
|
||||
LL | OpenOptions::new().read(true).truncate(true).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -8,40 +8,55 @@ LL | OpenOptions::new().read(true).truncate(true).open("foo.txt");
|
|||
= help: to override `-D warnings` add `#[allow(clippy::nonsensical_open_options)]`
|
||||
|
||||
error: file opened with `append` and `truncate`
|
||||
--> $DIR/open_options.rs:9:5
|
||||
--> $DIR/open_options.rs:10:5
|
||||
|
|
||||
LL | OpenOptions::new().append(true).truncate(true).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: the method `read` is called more than once
|
||||
--> $DIR/open_options.rs:12:5
|
||||
--> $DIR/open_options.rs:13:5
|
||||
|
|
||||
LL | OpenOptions::new().read(true).read(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: the method `create` is called more than once
|
||||
--> $DIR/open_options.rs:14:5
|
||||
--> $DIR/open_options.rs:15:5
|
||||
|
|
||||
LL | OpenOptions::new().create(true).create(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: file opened with `create`, but `truncate` behavior not defined
|
||||
--> $DIR/open_options.rs:15:5
|
||||
|
|
||||
LL | OpenOptions::new().create(true).create(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `-D clippy::suspicious-open-options` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::suspicious_open_options)]`
|
||||
|
||||
error: the method `write` is called more than once
|
||||
--> $DIR/open_options.rs:16:5
|
||||
--> $DIR/open_options.rs:17:5
|
||||
|
|
||||
LL | OpenOptions::new().write(true).write(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: the method `append` is called more than once
|
||||
--> $DIR/open_options.rs:18:5
|
||||
--> $DIR/open_options.rs:19:5
|
||||
|
|
||||
LL | OpenOptions::new().append(true).append(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: the method `truncate` is called more than once
|
||||
--> $DIR/open_options.rs:20:5
|
||||
--> $DIR/open_options.rs:21:5
|
||||
|
|
||||
LL | OpenOptions::new().truncate(true).truncate(false).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
error: file opened with `create`, but `truncate` behavior not defined
|
||||
--> $DIR/open_options.rs:23:5
|
||||
|
|
||||
LL | OpenOptions::new().create(true).open("foo.txt");
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 9 previous errors
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ fn main() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
@ -104,6 +105,7 @@ fn msrv_1_54() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
@ -124,6 +126,7 @@ fn msrv_1_55() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ fn main() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
@ -104,6 +105,7 @@ fn msrv_1_54() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
@ -124,6 +126,7 @@ fn msrv_1_55() {
|
|||
.write(true)
|
||||
.read(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open("foo.txt")
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ LL | t.seek(SeekFrom::Start(0));
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
|
||||
|
||||
error: used `seek` to go to the start of the stream
|
||||
--> $DIR/seek_to_start_instead_of_rewind.rs:133:7
|
||||
--> $DIR/seek_to_start_instead_of_rewind.rs:136:7
|
||||
|
|
||||
LL | f.seek(SeekFrom::Start(0));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `rewind()`
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue