Skip to content

Commit b96639f

Browse files
authored
Merge pull request #2143 from HMPerson1/master
Add lint for fallible impls of `From`
2 parents 6d89798 + 7206023 commit b96639f

File tree

5 files changed

+290
-0
lines changed

5 files changed

+290
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use rustc::lint::*;
2+
use rustc::hir;
3+
use rustc::ty;
4+
use syntax_pos::Span;
5+
use utils::{method_chain_args, match_def_path, span_lint_and_then, walk_ptrs_ty};
6+
use utils::paths::{BEGIN_PANIC, BEGIN_PANIC_FMT, FROM_TRAIT, OPTION, RESULT};
7+
8+
/// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
9+
///
10+
/// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure.
11+
///
12+
/// **Known problems:** None.
13+
///
14+
/// **Example:**
15+
/// ```rust
16+
/// struct Foo(i32);
17+
/// impl From<String> for Foo {
18+
/// fn from(s: String) -> Self {
19+
/// Foo(s.parse().unwrap())
20+
/// }
21+
/// }
22+
/// ```
23+
declare_lint! {
24+
pub FALLIBLE_IMPL_FROM, Allow,
25+
"Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
26+
}
27+
28+
pub struct FallibleImplFrom;
29+
30+
impl LintPass for FallibleImplFrom {
31+
fn get_lints(&self) -> LintArray {
32+
lint_array!(FALLIBLE_IMPL_FROM)
33+
}
34+
}
35+
36+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FallibleImplFrom {
37+
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
38+
// check for `impl From<???> for ..`
39+
let impl_def_id = cx.tcx.hir.local_def_id(item.id);
40+
if_let_chain!{[
41+
let hir::ItemImpl(.., ref impl_items) = item.node,
42+
let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_def_id),
43+
match_def_path(cx.tcx, impl_trait_ref.def_id, &FROM_TRAIT),
44+
], {
45+
lint_impl_body(cx, item.span, impl_items);
46+
}}
47+
}
48+
}
49+
50+
fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_items: &hir::HirVec<hir::ImplItemRef>) {
51+
use rustc::hir::*;
52+
use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};
53+
54+
struct FindPanicUnwrap<'a, 'tcx: 'a> {
55+
tcx: ty::TyCtxt<'a, 'tcx, 'tcx>,
56+
tables: &'tcx ty::TypeckTables<'tcx>,
57+
result: Vec<Span>,
58+
}
59+
60+
impl<'a, 'tcx: 'a> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
61+
fn visit_expr(&mut self, expr: &'tcx Expr) {
62+
// check for `begin_panic`
63+
if_let_chain!{[
64+
let ExprCall(ref func_expr, _) = expr.node,
65+
let ExprPath(QPath::Resolved(_, ref path)) = func_expr.node,
66+
match_def_path(self.tcx, path.def.def_id(), &BEGIN_PANIC) ||
67+
match_def_path(self.tcx, path.def.def_id(), &BEGIN_PANIC_FMT),
68+
], {
69+
self.result.push(expr.span);
70+
}}
71+
72+
// check for `unwrap`
73+
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
74+
let reciever_ty = walk_ptrs_ty(self.tables.expr_ty(&arglists[0][0]));
75+
if match_type(self.tcx, reciever_ty, &OPTION) ||
76+
match_type(self.tcx, reciever_ty, &RESULT)
77+
{
78+
self.result.push(expr.span);
79+
}
80+
}
81+
82+
// and check sub-expressions
83+
intravisit::walk_expr(self, expr);
84+
}
85+
86+
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
87+
NestedVisitorMap::None
88+
}
89+
}
90+
91+
for impl_item in impl_items {
92+
if_let_chain!{[
93+
impl_item.name == "from",
94+
let ImplItemKind::Method(_, body_id) =
95+
cx.tcx.hir.impl_item(impl_item.id).node,
96+
], {
97+
// check the body for `begin_panic` or `unwrap`
98+
let body = cx.tcx.hir.body(body_id);
99+
let impl_item_def_id = cx.tcx.hir.local_def_id(impl_item.id.node_id);
100+
let mut fpu = FindPanicUnwrap {
101+
tcx: cx.tcx,
102+
tables: cx.tcx.typeck_tables_of(impl_item_def_id),
103+
result: Vec::new(),
104+
};
105+
fpu.visit_expr(&body.value);
106+
107+
// if we've found one, lint
108+
if !fpu.result.is_empty() {
109+
span_lint_and_then(
110+
cx,
111+
FALLIBLE_IMPL_FROM,
112+
impl_span,
113+
"consider implementing `TryFrom` instead",
114+
move |db| {
115+
db.help(
116+
"`From` is intended for infallible conversions only. \
117+
Use `TryFrom` if there's a possibility for the conversion to fail.");
118+
db.span_note(fpu.result, "potential failure(s)");
119+
});
120+
}
121+
}}
122+
}
123+
}
124+
125+
fn match_type(tcx: ty::TyCtxt, ty: ty::Ty, path: &[&str]) -> bool {
126+
match ty.sty {
127+
ty::TyAdt(adt, _) => match_def_path(tcx, adt.did, path),
128+
_ => false,
129+
}
130+
}

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub mod identity_conversion;
100100
pub mod identity_op;
101101
pub mod if_let_redundant_pattern_matching;
102102
pub mod if_not_else;
103+
pub mod fallible_impl_from;
103104
pub mod infinite_iter;
104105
pub mod int_plus_one;
105106
pub mod invalid_ref;
@@ -341,6 +342,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
341342
reg.register_late_lint_pass(box identity_conversion::IdentityConversion::default());
342343
reg.register_late_lint_pass(box types::ImplicitHasher);
343344
reg.register_early_lint_pass(box const_static_lifetime::StaticConst);
345+
reg.register_late_lint_pass(box fallible_impl_from::FallibleImplFrom);
344346

345347
reg.register_lint_group("clippy_restrictions", vec![
346348
arithmetic::FLOAT_ARITHMETIC,
@@ -446,6 +448,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
446448
identity_conversion::IDENTITY_CONVERSION,
447449
identity_op::IDENTITY_OP,
448450
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
451+
fallible_impl_from::FALLIBLE_IMPL_FROM,
449452
infinite_iter::INFINITE_ITER,
450453
invalid_ref::INVALID_REF,
451454
is_unit_expr::UNIT_EXPR,

clippy_lints/src/utils/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub const ARC: [&str; 3] = ["alloc", "arc", "Arc"];
66
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
77
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
88
pub const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
9+
pub const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
910
pub const BINARY_HEAP: [&str; 3] = ["alloc", "binary_heap", "BinaryHeap"];
1011
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
1112
pub const BOX: [&str; 3] = ["std", "boxed", "Box"];
@@ -27,6 +28,7 @@ pub const DROP: [&str; 3] = ["core", "mem", "drop"];
2728
pub const FMT_ARGUMENTS_NEWV1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
2829
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
2930
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
31+
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
3032
pub const HASH: [&str; 2] = ["hash", "Hash"];
3133
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
3234
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];

tests/ui/fallible_impl_from.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#![deny(fallible_impl_from)]
2+
3+
// docs example
4+
struct Foo(i32);
5+
impl From<String> for Foo {
6+
fn from(s: String) -> Self {
7+
Foo(s.parse().unwrap())
8+
}
9+
}
10+
11+
12+
struct Valid(Vec<u8>);
13+
14+
impl<'a> From<&'a str> for Valid {
15+
fn from(s: &'a str) -> Valid {
16+
Valid(s.to_owned().into_bytes())
17+
}
18+
}
19+
impl From<usize> for Valid {
20+
fn from(i: usize) -> Valid {
21+
Valid(Vec::with_capacity(i))
22+
}
23+
}
24+
25+
26+
struct Invalid;
27+
28+
impl From<usize> for Invalid {
29+
fn from(i: usize) -> Invalid {
30+
if i != 42 {
31+
panic!();
32+
}
33+
Invalid
34+
}
35+
}
36+
37+
impl From<Option<String>> for Invalid {
38+
fn from(s: Option<String>) -> Invalid {
39+
let s = s.unwrap();
40+
if !s.is_empty() {
41+
panic!(42);
42+
} else if s.parse::<u32>().unwrap() != 42 {
43+
panic!("{:?}", s);
44+
}
45+
Invalid
46+
}
47+
}
48+
49+
trait ProjStrTrait {
50+
type ProjString;
51+
}
52+
impl<T> ProjStrTrait for Box<T> {
53+
type ProjString = String;
54+
}
55+
impl<'a> From<&'a mut <Box<u32> as ProjStrTrait>::ProjString> for Invalid {
56+
fn from(s: &'a mut <Box<u32> as ProjStrTrait>::ProjString) -> Invalid {
57+
if s.parse::<u32>().ok().unwrap() != 42 {
58+
panic!("{:?}", s);
59+
}
60+
Invalid
61+
}
62+
}
63+
64+
fn main() {}

tests/ui/fallible_impl_from.stderr

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
error: consider implementing `TryFrom` instead
2+
--> $DIR/fallible_impl_from.rs:5:1
3+
|
4+
5 | / impl From<String> for Foo {
5+
6 | | fn from(s: String) -> Self {
6+
7 | | Foo(s.parse().unwrap())
7+
8 | | }
8+
9 | | }
9+
| |_^
10+
|
11+
note: lint level defined here
12+
--> $DIR/fallible_impl_from.rs:1:9
13+
|
14+
1 | #![deny(fallible_impl_from)]
15+
| ^^^^^^^^^^^^^^^^^^
16+
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
17+
note: potential failure(s)
18+
--> $DIR/fallible_impl_from.rs:7:13
19+
|
20+
7 | Foo(s.parse().unwrap())
21+
| ^^^^^^^^^^^^^^^^^^
22+
23+
error: consider implementing `TryFrom` instead
24+
--> $DIR/fallible_impl_from.rs:28:1
25+
|
26+
28 | / impl From<usize> for Invalid {
27+
29 | | fn from(i: usize) -> Invalid {
28+
30 | | if i != 42 {
29+
31 | | panic!();
30+
... |
31+
34 | | }
32+
35 | | }
33+
| |_^
34+
|
35+
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
36+
note: potential failure(s)
37+
--> $DIR/fallible_impl_from.rs:31:13
38+
|
39+
31 | panic!();
40+
| ^^^^^^^^^
41+
= note: this error originates in a macro outside of the current crate
42+
43+
error: consider implementing `TryFrom` instead
44+
--> $DIR/fallible_impl_from.rs:37:1
45+
|
46+
37 | / impl From<Option<String>> for Invalid {
47+
38 | | fn from(s: Option<String>) -> Invalid {
48+
39 | | let s = s.unwrap();
49+
40 | | if !s.is_empty() {
50+
... |
51+
46 | | }
52+
47 | | }
53+
| |_^
54+
|
55+
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
56+
note: potential failure(s)
57+
--> $DIR/fallible_impl_from.rs:39:17
58+
|
59+
39 | let s = s.unwrap();
60+
| ^^^^^^^^^^
61+
40 | if !s.is_empty() {
62+
41 | panic!(42);
63+
| ^^^^^^^^^^^
64+
42 | } else if s.parse::<u32>().unwrap() != 42 {
65+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
66+
43 | panic!("{:?}", s);
67+
| ^^^^^^^^^^^^^^^^^^
68+
= note: this error originates in a macro outside of the current crate
69+
70+
error: consider implementing `TryFrom` instead
71+
--> $DIR/fallible_impl_from.rs:55:1
72+
|
73+
55 | / impl<'a> From<&'a mut <Box<u32> as ProjStrTrait>::ProjString> for Invalid {
74+
56 | | fn from(s: &'a mut <Box<u32> as ProjStrTrait>::ProjString) -> Invalid {
75+
57 | | if s.parse::<u32>().ok().unwrap() != 42 {
76+
58 | | panic!("{:?}", s);
77+
... |
78+
61 | | }
79+
62 | | }
80+
| |_^
81+
|
82+
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
83+
note: potential failure(s)
84+
--> $DIR/fallible_impl_from.rs:57:12
85+
|
86+
57 | if s.parse::<u32>().ok().unwrap() != 42 {
87+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88+
58 | panic!("{:?}", s);
89+
| ^^^^^^^^^^^^^^^^^^
90+
= note: this error originates in a macro outside of the current crate
91+

0 commit comments

Comments
 (0)