Skip to content

Commit 7644f8e

Browse files
Add "nonsensical OpenOptions" lint
1 parent 3e475e9 commit 7644f8e

File tree

5 files changed

+166
-6
lines changed

5 files changed

+166
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
66
[Jump to usage instructions](#usage)
77

88
##Lints
9-
There are 59 lints included in this crate:
9+
There are 60 lints included in this crate:
1010

1111
name | default | meaning
1212
-------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -42,6 +42,7 @@ name
4242
[needless_range_loop](https://github.com/Manishearth/rust-clippy/wiki#needless_range_loop) | warn | for-looping over a range of indices where an iterator over items would do
4343
[needless_return](https://github.com/Manishearth/rust-clippy/wiki#needless_return) | warn | using a return statement like `return expr;` where an expression would suffice
4444
[non_ascii_literal](https://github.com/Manishearth/rust-clippy/wiki#non_ascii_literal) | allow | using any literal non-ASCII chars in a string literal; suggests using the \\u escape instead
45+
[nonsensical_open_options](https://github.com/Manishearth/rust-clippy/wiki#nonsensical_open_options) | warn | The options used for opening a file are nonsensical
4546
[option_unwrap_used](https://github.com/Manishearth/rust-clippy/wiki#option_unwrap_used) | allow | using `Option.unwrap()`, which should at least get a better message using `expect()`
4647
[precedence](https://github.com/Manishearth/rust-clippy/wiki#precedence) | warn | catches operations where precedence may be unclear. See the wiki for a list of cases caught
4748
[ptr_arg](https://github.com/Manishearth/rust-clippy/wiki#ptr_arg) | allow | fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub mod loops;
4747
pub mod ranges;
4848
pub mod matches;
4949
pub mod precedence;
50+
pub mod open_options;
5051

5152
mod reexport {
5253
pub use syntax::ast::{Name, Ident, NodeId};
@@ -88,6 +89,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
8889
reg.register_late_lint_pass(box matches::MatchPass);
8990
reg.register_late_lint_pass(box misc::PatternPass);
9091
reg.register_late_lint_pass(box minmax::MinMaxPass);
92+
reg.register_late_lint_pass(box open_options::NonSensicalOpenOptions);
9193

9294
reg.register_lint_group("clippy_pedantic", vec![
9395
methods::OPTION_UNWRAP_USED,
@@ -142,6 +144,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
142144
misc::TOPLEVEL_REF_ARG,
143145
mut_reference::UNNECESSARY_MUT_PASSED,
144146
needless_bool::NEEDLESS_BOOL,
147+
open_options::NONSENSICAL_OPEN_OPTIONS,
145148
precedence::PRECEDENCE,
146149
ranges::RANGE_STEP_BY_ZERO,
147150
returns::LET_AND_RETURN,

src/open_options.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use rustc::lint::*;
2+
use rustc_front::hir::{Expr, ExprMethodCall, ExprLit};
3+
use utils::{walk_ptrs_ty_depth, match_type, span_lint, OPEN_OPTIONS_PATH};
4+
use syntax::codemap::{Span, Spanned};
5+
use syntax::ast::Lit_::LitBool;
6+
7+
declare_lint! {
8+
pub NONSENSICAL_OPEN_OPTIONS,
9+
Warn,
10+
"The options used for opening a file are nonsensical"
11+
}
12+
13+
14+
#[derive(Copy,Clone)]
15+
pub struct NonSensicalOpenOptions;
16+
17+
impl LintPass for NonSensicalOpenOptions {
18+
fn get_lints(&self) -> LintArray {
19+
lint_array!(NONSENSICAL_OPEN_OPTIONS)
20+
}
21+
}
22+
23+
impl LateLintPass for NonSensicalOpenOptions {
24+
fn check_expr(&mut self, cx: &LateContext, e: &Expr) {
25+
if let ExprMethodCall(ref name, _, ref arguments) = e.node {
26+
let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
27+
if name.node.as_str() == "open" && match_type(cx, obj_ty, &OPEN_OPTIONS_PATH){
28+
let mut options = Vec::new();
29+
get_open_options(cx, &arguments[0], &mut options);
30+
check_open_options(cx, &options, e.span);
31+
}
32+
}
33+
}
34+
}
35+
36+
#[derive(Debug)]
37+
enum Argument {
38+
True,
39+
False,
40+
Unknown
41+
}
42+
43+
#[derive(Debug)]
44+
enum OpenOption {
45+
Write(Argument),
46+
Read(Argument),
47+
Truncate(Argument),
48+
Create(Argument),
49+
Append(Argument)
50+
}
51+
52+
fn get_open_options(cx: &LateContext, argument: &Expr, options: &mut Vec<OpenOption>) {
53+
if let ExprMethodCall(ref name, _, ref arguments) = argument.node {
54+
let (obj_ty, _) = walk_ptrs_ty_depth(cx.tcx.expr_ty(&arguments[0]));
55+
56+
// Only proceed if this is a call on some object of type std::fs::OpenOptions
57+
if match_type(cx, obj_ty, &OPEN_OPTIONS_PATH) && arguments.len() >= 2 {
58+
59+
let argument_option = match arguments[1].node {
60+
ExprLit(ref span) => {
61+
if let Spanned {node: LitBool(lit), span: _} = **span {
62+
if lit {Argument::True} else {Argument::False}
63+
} else {
64+
return; // The function is called with a literal
65+
// which is not a boolean literal. This is theoretically
66+
// possible, but not very likely.
67+
}
68+
},
69+
_ => {
70+
Argument::Unknown
71+
}
72+
};
73+
74+
match &*name.node.as_str() {
75+
"create" => {
76+
options.push(OpenOption::Create(argument_option));
77+
},
78+
"append" => {
79+
options.push(OpenOption::Append(argument_option));
80+
},
81+
"truncate" => {
82+
options.push(OpenOption::Truncate(argument_option));
83+
},
84+
"read" => {
85+
options.push(OpenOption::Read(argument_option));
86+
},
87+
"write" => {
88+
options.push(OpenOption::Write(argument_option));
89+
},
90+
_ => {}
91+
}
92+
93+
get_open_options(cx, &arguments[0], options);
94+
}
95+
}
96+
}
97+
98+
fn check_for_duplicates(cx: &LateContext, options: &[OpenOption], span: Span) {
99+
// This code is almost duplicated (oh, the irony), but I haven't found a way to unify it.
100+
if options.iter().filter(|o| if let OpenOption::Create(_) = **o {true} else {false}).count() > 1 {
101+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"create\" \
102+
is called more than once");
103+
}
104+
if options.iter().filter(|o| if let OpenOption::Append(_) = **o {true} else {false}).count() > 1 {
105+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"append\" \
106+
is called more than once");
107+
}
108+
if options.iter().filter(|o| if let OpenOption::Truncate(_) = **o {true} else {false}).count() > 1 {
109+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"truncate\" \
110+
is called more than once");
111+
}
112+
if options.iter().filter(|o| if let OpenOption::Read(_) = **o {true} else {false}).count() > 1 {
113+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"read\" \
114+
is called more than once");
115+
}
116+
if options.iter().filter(|o| if let OpenOption::Write(_) = **o {true} else {false}).count() > 1 {
117+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "The method \"write\" \
118+
is called more than once");
119+
}
120+
}
121+
122+
fn check_for_inconsistencies(cx: &LateContext, options: &[OpenOption], span: Span) {
123+
// Truncate + read makes no sense.
124+
if options.iter().filter(|o| if let OpenOption::Read(Argument::True) = **o {true} else {false}).count() > 0 &&
125+
options.iter().filter(|o| if let OpenOption::Truncate(Argument::True) = **o {true} else {false}).count() > 0 {
126+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"truncate\" and \"read\"");
127+
}
128+
129+
// Append + truncate makes no sense.
130+
if options.iter().filter(|o| if let OpenOption::Append(Argument::True) = **o {true} else {false}).count() > 0 &&
131+
options.iter().filter(|o| if let OpenOption::Truncate(Argument::True) = **o {true} else {false}).count() > 0 {
132+
span_lint(cx, NONSENSICAL_OPEN_OPTIONS, span, "File opened with \"append\" and \"truncate\"");
133+
}
134+
}
135+
136+
fn check_open_options(cx: &LateContext, options: &[OpenOption], span: Span) {
137+
check_for_duplicates(cx, options, span);
138+
check_for_inconsistencies(cx, options, span);
139+
}

src/utils.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ use std::borrow::Cow;
99
use syntax::ast::Lit_::*;
1010

1111
// module DefPaths for certain structs/enums we check for
12-
pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
13-
pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
14-
pub const STRING_PATH: [&'static str; 3] = ["collections", "string", "String"];
15-
pub const VEC_PATH: [&'static str; 3] = ["collections", "vec", "Vec"];
16-
pub const LL_PATH: [&'static str; 3] = ["collections", "linked_list", "LinkedList"];
12+
pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
13+
pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
14+
pub const STRING_PATH: [&'static str; 3] = ["collections", "string", "String"];
15+
pub const VEC_PATH: [&'static str; 3] = ["collections", "vec", "Vec"];
16+
pub const LL_PATH: [&'static str; 3] = ["collections", "linked_list", "LinkedList"];
17+
pub const OPEN_OPTIONS_PATH: [&'static str; 3] = ["std", "fs", "OpenOptions"];
1718

1819
/// returns true this expn_info was expanded by any macro
1920
pub fn in_macro(cx: &LateContext, span: Span) -> bool {

tests/compile-fail/open_options.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![feature(plugin)]
2+
#![plugin(clippy)]
3+
use std::fs::OpenOptions;
4+
5+
#[allow(unused_must_use)]
6+
#[deny(nonsensical_open_options)]
7+
fn main() {
8+
OpenOptions::new().read(true).truncate(true).open("foo.txt"); //~ERROR File opened with "truncate" and "read"
9+
OpenOptions::new().append(true).truncate(true).open("foo.txt"); //~ERROR File opened with "append" and "truncate"
10+
11+
OpenOptions::new().read(true).read(false).open("foo.txt"); //~ERROR The method "read" is called more than once
12+
OpenOptions::new().create(true).create(false).open("foo.txt"); //~ERROR The method "create" is called more than once
13+
OpenOptions::new().write(true).write(false).open("foo.txt"); //~ERROR The method "write" is called more than once
14+
OpenOptions::new().append(true).append(false).open("foo.txt"); //~ERROR The method "append" is called more than once
15+
OpenOptions::new().truncate(true).truncate(false).open("foo.txt"); //~ERROR The method "truncate" is called more than once
16+
}

0 commit comments

Comments
 (0)