Skip to content

Add lint for fallible impls of From #2143

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

Merged
merged 2 commits into from
Oct 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions clippy_lints/src/fallible_impl_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use rustc::lint::*;
use rustc::hir;
use rustc::ty;
use syntax_pos::Span;
use utils::{method_chain_args, match_def_path, span_lint_and_then, walk_ptrs_ty};
use utils::paths::{BEGIN_PANIC, BEGIN_PANIC_FMT, FROM_TRAIT, OPTION, RESULT};

/// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
///
/// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// struct Foo(i32);
/// impl From<String> for Foo {
/// fn from(s: String) -> Self {
/// Foo(s.parse().unwrap())
/// }
/// }
/// ```
declare_lint! {
pub FALLIBLE_IMPL_FROM, Allow,
"Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
}

pub struct FallibleImplFrom;

impl LintPass for FallibleImplFrom {
fn get_lints(&self) -> LintArray {
lint_array!(FALLIBLE_IMPL_FROM)
}
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FallibleImplFrom {
fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
// check for `impl From<???> for ..`
let impl_def_id = cx.tcx.hir.local_def_id(item.id);
if_let_chain!{[
let hir::ItemImpl(.., ref impl_items) = item.node,
let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_def_id),
match_def_path(cx.tcx, impl_trait_ref.def_id, &FROM_TRAIT),
], {
lint_impl_body(cx, item.span, impl_items);
}}
}
}

fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_items: &hir::HirVec<hir::ImplItemRef>) {
use rustc::hir::*;
use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};

struct FindPanicUnwrap<'a, 'tcx: 'a> {
tcx: ty::TyCtxt<'a, 'tcx, 'tcx>,
tables: &'tcx ty::TypeckTables<'tcx>,
result: Vec<Span>,
}

impl<'a, 'tcx: 'a> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr) {
// check for `begin_panic`
if_let_chain!{[
let ExprCall(ref func_expr, _) = expr.node,
let ExprPath(QPath::Resolved(_, ref path)) = func_expr.node,
match_def_path(self.tcx, path.def.def_id(), &BEGIN_PANIC) ||
match_def_path(self.tcx, path.def.def_id(), &BEGIN_PANIC_FMT),
], {
self.result.push(expr.span);
}}

// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
let reciever_ty = walk_ptrs_ty(self.tables.expr_ty(&arglists[0][0]));
if match_type(self.tcx, reciever_ty, &OPTION) ||
match_type(self.tcx, reciever_ty, &RESULT)
{
self.result.push(expr.span);
}
}

// and check sub-expressions
intravisit::walk_expr(self, expr);
}

fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::None
}
}

for impl_item in impl_items {
if_let_chain!{[
impl_item.name == "from",
let ImplItemKind::Method(_, body_id) =
cx.tcx.hir.impl_item(impl_item.id).node,
], {
// check the body for `begin_panic` or `unwrap`
let body = cx.tcx.hir.body(body_id);
let impl_item_def_id = cx.tcx.hir.local_def_id(impl_item.id.node_id);
let mut fpu = FindPanicUnwrap {
tcx: cx.tcx,
tables: cx.tcx.typeck_tables_of(impl_item_def_id),
result: Vec::new(),
};
fpu.visit_expr(&body.value);

// if we've found one, lint
if !fpu.result.is_empty() {
span_lint_and_then(
cx,
FALLIBLE_IMPL_FROM,
impl_span,
"consider implementing `TryFrom` instead",
move |db| {
db.help(
"`From` is intended for infallible conversions only. \
Use `TryFrom` if there's a possibility for the conversion to fail.");
db.span_note(fpu.result, "potential failure(s)");
});
}
}}
}
}

fn match_type(tcx: ty::TyCtxt, ty: ty::Ty, path: &[&str]) -> bool {
match ty.sty {
ty::TyAdt(adt, _) => match_def_path(tcx, adt.did, path),
_ => false,
}
}
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ pub mod identity_conversion;
pub mod identity_op;
pub mod if_let_redundant_pattern_matching;
pub mod if_not_else;
pub mod fallible_impl_from;
pub mod infinite_iter;
pub mod int_plus_one;
pub mod invalid_ref;
Expand Down Expand Up @@ -341,6 +342,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
reg.register_late_lint_pass(box identity_conversion::IdentityConversion::default());
reg.register_late_lint_pass(box types::ImplicitHasher);
reg.register_early_lint_pass(box const_static_lifetime::StaticConst);
reg.register_late_lint_pass(box fallible_impl_from::FallibleImplFrom);

reg.register_lint_group("clippy_restrictions", vec![
arithmetic::FLOAT_ARITHMETIC,
Expand Down Expand Up @@ -446,6 +448,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
identity_conversion::IDENTITY_CONVERSION,
identity_op::IDENTITY_OP,
if_let_redundant_pattern_matching::IF_LET_REDUNDANT_PATTERN_MATCHING,
fallible_impl_from::FALLIBLE_IMPL_FROM,
infinite_iter::INFINITE_ITER,
invalid_ref::INVALID_REF,
is_unit_expr::UNIT_EXPR,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub const ARC: [&str; 3] = ["alloc", "arc", "Arc"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
pub const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
pub const BINARY_HEAP: [&str; 3] = ["alloc", "binary_heap", "BinaryHeap"];
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
pub const BOX: [&str; 3] = ["std", "boxed", "Box"];
Expand All @@ -27,6 +28,7 @@ pub const DROP: [&str; 3] = ["core", "mem", "drop"];
pub const FMT_ARGUMENTS_NEWV1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
pub const HASH: [&str; 2] = ["hash", "Hash"];
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
Expand Down
64 changes: 64 additions & 0 deletions tests/ui/fallible_impl_from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#![deny(fallible_impl_from)]

// docs example
struct Foo(i32);
impl From<String> for Foo {
fn from(s: String) -> Self {
Foo(s.parse().unwrap())
}
}


struct Valid(Vec<u8>);

impl<'a> From<&'a str> for Valid {
fn from(s: &'a str) -> Valid {
Valid(s.to_owned().into_bytes())
}
}
impl From<usize> for Valid {
fn from(i: usize) -> Valid {
Valid(Vec::with_capacity(i))
}
}


struct Invalid;

impl From<usize> for Invalid {
fn from(i: usize) -> Invalid {
if i != 42 {
panic!();
}
Invalid
}
}

impl From<Option<String>> for Invalid {
fn from(s: Option<String>) -> Invalid {
let s = s.unwrap();
if !s.is_empty() {
panic!(42);
} else if s.parse::<u32>().unwrap() != 42 {
panic!("{:?}", s);
}
Invalid
}
}

trait ProjStrTrait {
type ProjString;
}
impl<T> ProjStrTrait for Box<T> {
type ProjString = String;
}
impl<'a> From<&'a mut <Box<u32> as ProjStrTrait>::ProjString> for Invalid {
fn from(s: &'a mut <Box<u32> as ProjStrTrait>::ProjString) -> Invalid {
if s.parse::<u32>().ok().unwrap() != 42 {
panic!("{:?}", s);
}
Invalid
}
}

fn main() {}
91 changes: 91 additions & 0 deletions tests/ui/fallible_impl_from.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
error: consider implementing `TryFrom` instead
--> $DIR/fallible_impl_from.rs:5:1
|
5 | / impl From<String> for Foo {
6 | | fn from(s: String) -> Self {
7 | | Foo(s.parse().unwrap())
8 | | }
9 | | }
| |_^
|
note: lint level defined here
--> $DIR/fallible_impl_from.rs:1:9
|
1 | #![deny(fallible_impl_from)]
| ^^^^^^^^^^^^^^^^^^
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
note: potential failure(s)
--> $DIR/fallible_impl_from.rs:7:13
|
7 | Foo(s.parse().unwrap())
| ^^^^^^^^^^^^^^^^^^

error: consider implementing `TryFrom` instead
--> $DIR/fallible_impl_from.rs:28:1
|
28 | / impl From<usize> for Invalid {
29 | | fn from(i: usize) -> Invalid {
30 | | if i != 42 {
31 | | panic!();
... |
34 | | }
35 | | }
| |_^
|
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
note: potential failure(s)
--> $DIR/fallible_impl_from.rs:31:13
|
31 | panic!();
| ^^^^^^^^^
= note: this error originates in a macro outside of the current crate

error: consider implementing `TryFrom` instead
--> $DIR/fallible_impl_from.rs:37:1
|
37 | / impl From<Option<String>> for Invalid {
38 | | fn from(s: Option<String>) -> Invalid {
39 | | let s = s.unwrap();
40 | | if !s.is_empty() {
... |
46 | | }
47 | | }
| |_^
|
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
note: potential failure(s)
--> $DIR/fallible_impl_from.rs:39:17
|
39 | let s = s.unwrap();
| ^^^^^^^^^^
40 | if !s.is_empty() {
41 | panic!(42);
| ^^^^^^^^^^^
42 | } else if s.parse::<u32>().unwrap() != 42 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^
43 | panic!("{:?}", s);
| ^^^^^^^^^^^^^^^^^^
= note: this error originates in a macro outside of the current crate

error: consider implementing `TryFrom` instead
--> $DIR/fallible_impl_from.rs:55:1
|
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);
... |
61 | | }
62 | | }
| |_^
|
= help: `From` is intended for infallible conversions only. Use `TryFrom` if there's a possibility for the conversion to fail.
note: potential failure(s)
--> $DIR/fallible_impl_from.rs:57:12
|
57 | if s.parse::<u32>().ok().unwrap() != 42 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 | panic!("{:?}", s);
| ^^^^^^^^^^^^^^^^^^
= note: this error originates in a macro outside of the current crate