|
| 1 | +use crate::utils::{is_entrypoint_fn, match_def_path, paths, qpath_res, span_lint}; |
| 2 | +use if_chain::if_chain; |
| 3 | +use rustc::hir::{Expr, ExprKind, Item, ItemKind, Node}; |
| 4 | +use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; |
| 5 | +use rustc::{declare_lint_pass, declare_tool_lint}; |
| 6 | + |
| 7 | +declare_clippy_lint! { |
| 8 | + /// **What it does:** `exit()` terminates the program and doesn't provide a |
| 9 | + /// stack trace. |
| 10 | + /// |
| 11 | + /// **Why is this bad?** Ideally a program is terminated by finishing |
| 12 | + /// the main function. |
| 13 | + /// |
| 14 | + /// **Known problems:** None. |
| 15 | + /// |
| 16 | + /// **Example:** |
| 17 | + /// ```ignore |
| 18 | + /// std::process::exit(0) |
| 19 | + /// ``` |
| 20 | + pub EXIT, |
| 21 | + restriction, |
| 22 | + "`std::process::exit` is called, terminating the program" |
| 23 | +} |
| 24 | + |
| 25 | +declare_lint_pass!(Exit => [EXIT]); |
| 26 | + |
| 27 | +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Exit { |
| 28 | + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) { |
| 29 | + if_chain! { |
| 30 | + if let ExprKind::Call(ref path_expr, ref _args) = e.kind; |
| 31 | + if let ExprKind::Path(ref path) = path_expr.kind; |
| 32 | + if let Some(def_id) = qpath_res(cx, path, path_expr.hir_id).opt_def_id(); |
| 33 | + if match_def_path(cx, def_id, &paths::EXIT); |
| 34 | + then { |
| 35 | + let mut parent = cx.tcx.hir().get_parent_item(e.hir_id); |
| 36 | + if let Some(Node::Item(Item{ident, kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) { |
| 37 | + // If the next item up is a function we check if it is an entry point |
| 38 | + // and only then emit a linter warning |
| 39 | + let def_id = cx.tcx.hir().local_def_id(parent); |
| 40 | + if !is_entrypoint_fn(cx, def_id) { |
| 41 | + span_lint(cx, EXIT, e.span, "usage of `process::exit`"); |
| 42 | + } |
| 43 | + } |
| 44 | + } |
| 45 | + } |
| 46 | + } |
| 47 | +} |
0 commit comments