Skip to content
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

Add lint for assertions in functions returning Result #6280

Merged
merged 3 commits into from
Dec 7, 2020
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
68 changes: 31 additions & 37 deletions clippy_lints/src/panic_in_result_fn.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
use crate::utils::{is_expn_of, is_type_diagnostic_item, return_ty, span_lint_and_then};
use crate::utils::{find_macro_calls, is_type_diagnostic_item, return_ty, span_lint_and_then};
use rustc_hir as hir;
use rustc_hir::intravisit::{self, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::Expr;
use rustc_hir::intravisit::FnKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};

declare_clippy_lint! {
/// **What it does:** Checks for usage of `panic!`, `unimplemented!`, `todo!` or `unreachable!` in a function of type result.
/// **What it does:** Checks for usage of `panic!`, `unimplemented!`, `todo!`, `unreachable!` or assertions in a function of type result.
///
/// **Why is this bad?** For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence unimplemented, panic and unreachable should be avoided.
/// **Why is this bad?** For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided.
///
/// **Known problems:** None.
/// **Known problems:** Functions called from a function returning a `Result` may invoke a panicking macro. This is not checked.
///
/// **Example:**
///
Expand All @@ -22,9 +20,15 @@ declare_clippy_lint! {
/// panic!("error");
/// }
/// ```
/// Use instead:
/// ```rust
/// fn result_without_panic() -> Result<bool, String> {
/// Err(String::from("error"))
/// }
/// ```
pub PANIC_IN_RESULT_FN,
restriction,
"functions of type `Result<..>` that contain `panic!()`, `todo!()` or `unreachable()` or `unimplemented()` "
"functions of type `Result<..>` that contain `panic!()`, `todo!()`, `unreachable()`, `unimplemented()` or assertion"
}

declare_lint_pass!(PanicInResultFn => [PANIC_IN_RESULT_FN]);
Expand All @@ -47,43 +51,33 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
}
}

struct FindPanicUnimplementedUnreachable {
result: Vec<Span>,
}

impl<'tcx> Visitor<'tcx> for FindPanicUnimplementedUnreachable {
type Map = Map<'tcx>;

fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if ["unimplemented", "unreachable", "panic", "todo"]
.iter()
.any(|fun| is_expn_of(expr.span, fun).is_some())
{
self.result.push(expr.span);
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}

fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}

fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
let mut panics = FindPanicUnimplementedUnreachable { result: Vec::new() };
panics.visit_expr(&body.value);
if !panics.result.is_empty() {
let panics = find_macro_calls(
&[
"unimplemented",
"unreachable",
"panic",
"todo",
"assert",
"assert_eq",
"assert_ne",
"debug_assert",
"debug_assert_eq",
"debug_assert_ne",
],
body,
);
if !panics.is_empty() {
span_lint_and_then(
cx,
PANIC_IN_RESULT_FN,
impl_span,
"used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`",
"used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`",
move |diag| {
diag.help(
"`unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing",
"`unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing",
);
diag.span_note(panics.result, "return Err() instead of panicking");
diag.span_note(panics, "return Err() instead of panicking");
},
);
}
Expand Down
33 changes: 32 additions & 1 deletion clippy_lints/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::Node;
use rustc_hir::{
def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind,
Expand Down Expand Up @@ -603,6 +603,37 @@ pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
visitor.found
}

struct FindMacroCalls<'a, 'b> {
names: &'a [&'b str],
result: Vec<Span>,
}

impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> {
type Map = Map<'tcx>;

fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) {
self.result.push(expr.span);
}
// and check sub-expressions
intravisit::walk_expr(self, expr);
}

fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}

/// Finds calls of the specified macros in a function body.
pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec<Span> {
let mut fmc = FindMacroCalls {
names,
result: Vec::new(),
};
fmc.visit_expr(&body.value);
fmc.result
}

/// Converts a span to a code snippet if available, otherwise use default.
///
/// This is useful if you want to provide suggestions for your lint or more generally, if you want
Expand Down
24 changes: 12 additions & 12 deletions tests/ui/panic_in_result_fn.stderr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:7:5
|
LL | / fn result_with_panic() -> Result<bool, String> // should emit lint
Expand All @@ -8,15 +8,15 @@ LL | | }
| |_____^
|
= note: `-D clippy::panic-in-result-fn` implied by `-D warnings`
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:9:9
|
LL | panic!("error");
| ^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:12:5
|
LL | / fn result_with_unimplemented() -> Result<bool, String> // should emit lint
Expand All @@ -25,15 +25,15 @@ LL | | unimplemented!();
LL | | }
| |_____^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:14:9
|
LL | unimplemented!();
| ^^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:17:5
|
LL | / fn result_with_unreachable() -> Result<bool, String> // should emit lint
Expand All @@ -42,15 +42,15 @@ LL | | unreachable!();
LL | | }
| |_____^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:19:9
|
LL | unreachable!();
| ^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:22:5
|
LL | / fn result_with_todo() -> Result<bool, String> // should emit lint
Expand All @@ -59,15 +59,15 @@ LL | | todo!("Finish this");
LL | | }
| |_____^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:24:9
|
LL | todo!("Finish this");
| ^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:53:1
|
LL | / fn function_result_with_panic() -> Result<bool, String> // should emit lint
Expand All @@ -76,15 +76,15 @@ LL | | panic!("error");
LL | | }
| |_^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:55:5
|
LL | panic!("error");
| ^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` in a function that returns `Result`
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn.rs:68:1
|
LL | / fn main() -> Result<(), String> {
Expand All @@ -93,7 +93,7 @@ LL | | Ok(())
LL | | }
| |_^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()` or `panic!()` should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn.rs:69:5
|
Expand Down
48 changes: 48 additions & 0 deletions tests/ui/panic_in_result_fn_assertions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#![warn(clippy::panic_in_result_fn)]
#![allow(clippy::unnecessary_wraps)]

struct A;

impl A {
fn result_with_assert_with_message(x: i32) -> Result<bool, String> // should emit lint
{
assert!(x == 5, "wrong argument");
Ok(true)
}

fn result_with_assert_eq(x: i32) -> Result<bool, String> // should emit lint
{
assert_eq!(x, 5);
Ok(true)
}

fn result_with_assert_ne(x: i32) -> Result<bool, String> // should emit lint
{
assert_ne!(x, 1);
Ok(true)
}

fn other_with_assert_with_message(x: i32) // should not emit lint
{
assert!(x == 5, "wrong argument");
}

fn other_with_assert_eq(x: i32) // should not emit lint
{
assert_eq!(x, 5);
}

fn other_with_assert_ne(x: i32) // should not emit lint
{
assert_ne!(x, 1);
}

fn result_without_banned_functions() -> Result<bool, String> // should not emit lint
{
let assert = "assert!";
println!("No {}", assert);
Ok(true)
}
}

fn main() {}
57 changes: 57 additions & 0 deletions tests/ui/panic_in_result_fn_assertions.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn_assertions.rs:7:5
|
LL | / fn result_with_assert_with_message(x: i32) -> Result<bool, String> // should emit lint
LL | | {
LL | | assert!(x == 5, "wrong argument");
LL | | Ok(true)
LL | | }
| |_____^
|
= note: `-D clippy::panic-in-result-fn` implied by `-D warnings`
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn_assertions.rs:9:9
|
LL | assert!(x == 5, "wrong argument");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn_assertions.rs:13:5
|
LL | / fn result_with_assert_eq(x: i32) -> Result<bool, String> // should emit lint
LL | | {
LL | | assert_eq!(x, 5);
LL | | Ok(true)
LL | | }
| |_____^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn_assertions.rs:15:9
|
LL | assert_eq!(x, 5);
| ^^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result`
--> $DIR/panic_in_result_fn_assertions.rs:19:5
|
LL | / fn result_with_assert_ne(x: i32) -> Result<bool, String> // should emit lint
LL | | {
LL | | assert_ne!(x, 1);
LL | | Ok(true)
LL | | }
| |_____^
|
= help: `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertions should not be used in a function that returns `Result` as `Result` is expected to return an error instead of crashing
note: return Err() instead of panicking
--> $DIR/panic_in_result_fn_assertions.rs:21:9
|
LL | assert_ne!(x, 1);
| ^^^^^^^^^^^^^^^^^
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors

Loading