11use clippy_utils:: diagnostics:: span_lint;
2- use clippy_utils:: is_entrypoint_fn;
32use rustc_hir:: { Expr , ExprKind , Item , ItemKind , OwnerNode } ;
43use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
54use rustc_session:: declare_lint_pass;
65use rustc_span:: sym;
76
87declare_clippy_lint ! {
98 /// ### What it does
10- /// Detects calls to the `exit()` function which terminates the program.
9+ /// Detects calls to the `exit()` function that are not in the `main` function. Calls to `exit()`
10+ /// immediately terminate the program.
1111 ///
1212 /// ### Why restrict this?
1313 /// `exit()` immediately terminates the program with no information other than an exit code.
1414 /// This provides no means to troubleshoot a problem, and may be an unexpected side effect.
1515 ///
1616 /// Codebases may use this lint to require that all exits are performed either by panicking
1717 /// (which produces a message, a code location, and optionally a backtrace)
18- /// or by returning from `main()` (which is a single place to look).
18+ /// or by calling `exit()` from `main()` (which is a single place to look).
1919 ///
20- /// ### Example
20+ /// ### Good example
2121 /// ```no_run
22- /// std::process::exit(0)
22+ /// fn main() {
23+ /// std::process::exit(0);
24+ /// }
25+ /// ```
26+ ///
27+ /// ### Bad example
28+ /// ```no_run
29+ /// fn main() {
30+ /// other_function();
31+ /// }
32+ ///
33+ /// fn other_function() {
34+ /// std::process::exit(0);
35+ /// }
2336 /// ```
2437 ///
2538 /// Use instead:
@@ -36,7 +49,7 @@ declare_clippy_lint! {
3649 #[ clippy:: version = "1.41.0" ]
3750 pub EXIT ,
3851 restriction,
39- "detects `std::process::exit` calls"
52+ "detects `std::process::exit` calls outside of `main` "
4053}
4154
4255declare_lint_pass ! ( Exit => [ EXIT ] ) ;
@@ -52,10 +65,14 @@ impl<'tcx> LateLintPass<'tcx> for Exit {
5265 && let Some ( def_id) = cx. qpath_res ( path, path_expr. hir_id ) . opt_def_id ( )
5366 && cx. tcx . is_diagnostic_item ( sym:: process_exit, def_id)
5467 && let parent = cx. tcx . hir_get_parent_item ( e. hir_id )
55- && let OwnerNode :: Item ( Item { kind : ItemKind :: Fn { .. } , ..} ) = cx. tcx . hir_owner_node ( parent)
56- // If the next item up is a function we check if it is an entry point
68+ && let OwnerNode :: Item ( Item { kind : ItemKind :: Fn { ident , .. } , ..} ) = cx. tcx . hir_owner_node ( parent)
69+ // If the next item up is a function we check if it isn't named "main"
5770 // and only then emit a linter warning
58- && !is_entrypoint_fn ( cx, parent. to_def_id ( ) )
71+
72+ // if you instead check for the parent of the `exit()` call being the entrypoint function, as this worked before,
73+ // in compilation contexts like --all-targets (which include --tests), you get false positives
74+ // because in a test context, main is not the entrypoint function
75+ && ident. name . as_str ( ) != "main"
5976 {
6077 span_lint ( cx, EXIT , e. span , "usage of `process::exit`" ) ;
6178 }
0 commit comments