From 2b93d2cca61ede7af6201dc7a2be7c6957ebfb77 Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Mon, 7 Jul 2025 10:38:29 -0400 Subject: [PATCH 1/7] skip exit late lint pass on tests When using the `--test` or `--all-targets` flag, the exit lint should not fail on the main function. --- clippy_lints/src/exit.rs | 4 +++- tests/ui/exit4.rs | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/ui/exit4.rs diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index cc8e4d7d9e28..862dd6d64762 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::is_entrypoint_fn; use rustc_hir::{Expr, ExprKind, Item, ItemKind, OwnerNode}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -43,6 +43,8 @@ declare_lint_pass!(Exit => [EXIT]); impl<'tcx> LateLintPass<'tcx> for Exit { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if cx.sess().is_test_crate() { return; } + if let ExprKind::Call(path_expr, [_]) = e.kind && let ExprKind::Path(ref path) = path_expr.kind && let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id() diff --git a/tests/ui/exit4.rs b/tests/ui/exit4.rs new file mode 100644 index 000000000000..d52cdbce0eac --- /dev/null +++ b/tests/ui/exit4.rs @@ -0,0 +1,8 @@ +//@ check-pass +//@compile-flags: --test + +#![warn(clippy::exit)] + +fn main() { + std::process::exit(0) +} \ No newline at end of file From 1f36d4df9369b5665e936e812b587e1c6bfa0e28 Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Mon, 7 Jul 2025 11:08:45 -0400 Subject: [PATCH 2/7] run `cargo dev fmt` on changes --- clippy_lints/src/exit.rs | 4 +++- tests/ui/exit4.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 862dd6d64762..e6f1b7d89540 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -43,7 +43,9 @@ declare_lint_pass!(Exit => [EXIT]); impl<'tcx> LateLintPass<'tcx> for Exit { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if cx.sess().is_test_crate() { return; } + if cx.sess().is_test_crate() { + return; + } if let ExprKind::Call(path_expr, [_]) = e.kind && let ExprKind::Path(ref path) = path_expr.kind diff --git a/tests/ui/exit4.rs b/tests/ui/exit4.rs index d52cdbce0eac..821a26fd78b0 100644 --- a/tests/ui/exit4.rs +++ b/tests/ui/exit4.rs @@ -5,4 +5,4 @@ fn main() { std::process::exit(0) -} \ No newline at end of file +} From c000a0136ccec78d3a7d6cf5ad0422eeb13bf9eb Mon Sep 17 00:00:00 2001 From: Ses Goe Date: Mon, 7 Jul 2025 15:08:38 -0400 Subject: [PATCH 3/7] fix: check against 'main' function name instead of entrypoint function update docs to be a bit more clear --- clippy_lints/src/exit.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index e6f1b7d89540..0f63b36d5d2f 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_entrypoint_fn; use rustc_hir::{Expr, ExprKind, Item, ItemKind, OwnerNode}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; @@ -7,7 +6,8 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Detects calls to the `exit()` function which terminates the program. + /// Detects calls to the `exit()` function that are not in the `main` function. Calls to `exit()` + /// immediately terminate the program. /// /// ### Why restrict this? /// `exit()` immediately terminates the program with no information other than an exit code. @@ -15,11 +15,24 @@ declare_clippy_lint! { /// /// Codebases may use this lint to require that all exits are performed either by panicking /// (which produces a message, a code location, and optionally a backtrace) - /// or by returning from `main()` (which is a single place to look). + /// or by calling `exit()` from `main()` (which is a single place to look). /// - /// ### Example + /// ### Good example /// ```no_run - /// std::process::exit(0) + /// fn main() { + /// std::process::exit(0); + /// } + /// ``` + /// + /// ### Bad example + /// ```no_run + /// fn main() { + /// other_function(); + /// } + /// + /// fn other_function() { + /// std::process::exit(0); + /// } /// ``` /// /// Use instead: @@ -36,7 +49,7 @@ declare_clippy_lint! { #[clippy::version = "1.41.0"] pub EXIT, restriction, - "detects `std::process::exit` calls" + "detects `std::process::exit` calls outside of `main`" } declare_lint_pass!(Exit => [EXIT]); @@ -52,10 +65,14 @@ impl<'tcx> LateLintPass<'tcx> for Exit { && let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::process_exit, def_id) && let parent = cx.tcx.hir_get_parent_item(e.hir_id) - && let OwnerNode::Item(Item{kind: ItemKind::Fn{ .. }, ..}) = cx.tcx.hir_owner_node(parent) - // If the next item up is a function we check if it is an entry point + && let OwnerNode::Item(Item{kind: ItemKind::Fn{ ident, .. }, ..}) = cx.tcx.hir_owner_node(parent) + // If the next item up is a function we check if it isn't named "main" // and only then emit a linter warning - && !is_entrypoint_fn(cx, parent.to_def_id()) + + // if you instead check for the parent of the `exit()` call being the entrypoint function, as this worked before, + // in compilation contexts like --all-targets (which include --tests), you get false positives + // because in a test context, main is not the entrypoint function + && ident.name.as_str() != "main" { span_lint(cx, EXIT, e.span, "usage of `process::exit`"); } From 5853e6fa7e951a880dea88fc1076ef43827e6c32 Mon Sep 17 00:00:00 2001 From: Ses Goe Date: Mon, 7 Jul 2025 15:08:38 -0400 Subject: [PATCH 4/7] chore: add tests to check against the --test compile flag --- tests/ui/exit1_compile_flag_test.rs | 17 +++++++++++++++++ tests/ui/exit1_compile_flag_test.stderr | 11 +++++++++++ tests/ui/exit2_compile_flag_test.rs | 15 +++++++++++++++ tests/ui/exit2_compile_flag_test.stderr | 11 +++++++++++ tests/ui/exit3_compile_flag_test.rs | 11 +++++++++++ 5 files changed, 65 insertions(+) create mode 100644 tests/ui/exit1_compile_flag_test.rs create mode 100644 tests/ui/exit1_compile_flag_test.stderr create mode 100644 tests/ui/exit2_compile_flag_test.rs create mode 100644 tests/ui/exit2_compile_flag_test.stderr create mode 100644 tests/ui/exit3_compile_flag_test.rs diff --git a/tests/ui/exit1_compile_flag_test.rs b/tests/ui/exit1_compile_flag_test.rs new file mode 100644 index 000000000000..9f83ed325336 --- /dev/null +++ b/tests/ui/exit1_compile_flag_test.rs @@ -0,0 +1,17 @@ +//@compile-flags: --test +#![warn(clippy::exit)] + +fn not_main() { + if true { + std::process::exit(4); + //~^ exit + } +} + +fn main() { + if true { + std::process::exit(2); + }; + not_main(); + std::process::exit(1); +} diff --git a/tests/ui/exit1_compile_flag_test.stderr b/tests/ui/exit1_compile_flag_test.stderr new file mode 100644 index 000000000000..6e33c39f0d7b --- /dev/null +++ b/tests/ui/exit1_compile_flag_test.stderr @@ -0,0 +1,11 @@ +error: usage of `process::exit` + --> tests/ui/exit1_compile_flag_test.rs:6:9 + | +LL | std::process::exit(4); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::exit)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/exit2_compile_flag_test.rs b/tests/ui/exit2_compile_flag_test.rs new file mode 100644 index 000000000000..0b994ebc56c3 --- /dev/null +++ b/tests/ui/exit2_compile_flag_test.rs @@ -0,0 +1,15 @@ +//@compile-flags: --test +#![warn(clippy::exit)] + +fn also_not_main() { + std::process::exit(3); + //~^ exit +} + +fn main() { + if true { + std::process::exit(2); + }; + also_not_main(); + std::process::exit(1); +} diff --git a/tests/ui/exit2_compile_flag_test.stderr b/tests/ui/exit2_compile_flag_test.stderr new file mode 100644 index 000000000000..51eb26e9c2a4 --- /dev/null +++ b/tests/ui/exit2_compile_flag_test.stderr @@ -0,0 +1,11 @@ +error: usage of `process::exit` + --> tests/ui/exit2_compile_flag_test.rs:5:5 + | +LL | std::process::exit(3); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::exit` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::exit)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/exit3_compile_flag_test.rs b/tests/ui/exit3_compile_flag_test.rs new file mode 100644 index 000000000000..f8131ead2da3 --- /dev/null +++ b/tests/ui/exit3_compile_flag_test.rs @@ -0,0 +1,11 @@ +//@ check-pass +//@compile-flags: --test + +#![warn(clippy::exit)] + +fn main() { + if true { + std::process::exit(2); + }; + std::process::exit(1); +} From 1c7fb0ff918f39621c0e1b27c89f6a9a6e8efc70 Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Mon, 7 Jul 2025 15:59:38 -0400 Subject: [PATCH 5/7] do not completely suppress warning with test code --- clippy_lints/src/exit.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 0f63b36d5d2f..248094a0b25d 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -56,10 +56,6 @@ declare_lint_pass!(Exit => [EXIT]); impl<'tcx> LateLintPass<'tcx> for Exit { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if cx.sess().is_test_crate() { - return; - } - if let ExprKind::Call(path_expr, [_]) = e.kind && let ExprKind::Path(ref path) = path_expr.kind && let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id() From 9580d61dc52b08e2986eb4005f2cd991f112718e Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Mon, 7 Jul 2025 16:05:03 -0400 Subject: [PATCH 6/7] remove no longer used import --- clippy_lints/src/exit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 248094a0b25d..a43d788fc16f 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use rustc_hir::{Expr, ExprKind, Item, ItemKind, OwnerNode}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; From 81dd4e61ed8b63d3398a4bdee033edccc75d2994 Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Mon, 7 Jul 2025 16:17:19 -0400 Subject: [PATCH 7/7] use preinterned symbol fixes https://github.com/rust-lang/rust-clippy/actions/runs/16126736130/job/45505355614?pr=15222#logs --- clippy_lints/src/exit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index a43d788fc16f..487db69027af 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for Exit { // if you instead check for the parent of the `exit()` call being the entrypoint function, as this worked before, // in compilation contexts like --all-targets (which include --tests), you get false positives // because in a test context, main is not the entrypoint function - && ident.name.as_str() != "main" + && ident.name != sym::main { span_lint(cx, EXIT, e.span, "usage of `process::exit`"); }