@@ -2,12 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
22use clippy_utils:: visitors:: for_each_local_use_after_expr;
33use clippy_utils:: { fn_def_id, get_enclosing_block, match_any_def_paths, match_def_path, paths} ;
44use rustc_ast:: Mutability ;
5- use rustc_hir :: def :: DefKind ;
5+ use rustc_errors :: Applicability ;
66use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
77use rustc_hir:: { Expr , ExprKind , HirId , Local , Node , PatKind , Stmt , StmtKind } ;
88use rustc_lint:: { LateContext , LateLintPass } ;
9- use rustc_session:: { declare_lint_pass, declare_tool_lint } ;
10- use rustc_span:: { sym, Span } ;
9+ use rustc_session:: declare_lint_pass;
10+ use rustc_span:: sym;
1111use std:: ops:: ControlFlow ;
1212
1313declare_clippy_lint ! {
@@ -41,19 +41,6 @@ declare_clippy_lint! {
4141}
4242declare_lint_pass ! ( ZombieProcesses => [ ZOMBIE_PROCESSES ] ) ;
4343
44- fn emit_lint ( cx : & LateContext < ' _ > , span : Span ) {
45- span_lint_and_then (
46- cx,
47- ZOMBIE_PROCESSES ,
48- span,
49- "spawned process is never `wait()`-ed on and leaves behind a zombie process" ,
50- |diag| {
51- diag. help ( "consider calling `.wait()`" )
52- . note ( "also see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning" ) ;
53- } ,
54- ) ;
55- }
56-
5744impl < ' tcx > LateLintPass < ' tcx > for ZombieProcesses {
5845 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
5946 if let ExprKind :: Call ( ..) | ExprKind :: MethodCall ( ..) = expr. kind
@@ -62,7 +49,6 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
6249 {
6350 match cx. tcx . hir ( ) . get_parent ( expr. hir_id ) {
6451 Node :: Local ( local) if let PatKind :: Binding ( _, local_id, ..) = local. pat . kind => {
65-
6652 // If the `Child` is assigned to a variable, we want to check if the code never calls `.wait()`
6753 // on the variable, and lint if not.
6854 // This is difficult to do because expressions can be arbitrarily complex
@@ -73,46 +59,53 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
7359 // - calling `id` or `kill`
7460 // - no use at all (e.g. `let _x = child;`)
7561 // - taking a shared reference (`&`), `wait()` can't go through that
76- // Neither of these is sufficient to prevent zombie processes
62+ // None of these are sufficient to prevent zombie processes
7763 // Doing it like this means more FNs, but FNs are better than FPs.
7864 let has_no_wait = for_each_local_use_after_expr ( cx, local_id, expr. hir_id , |expr| {
7965 match cx. tcx . hir ( ) . get_parent ( expr. hir_id ) {
80- Node :: Stmt ( Stmt { kind : StmtKind :: Semi ( _) , .. } ) => ControlFlow :: Continue ( ( ) ) ,
66+ Node :: Stmt ( Stmt {
67+ kind : StmtKind :: Semi ( _) ,
68+ ..
69+ } ) => ControlFlow :: Continue ( ( ) ) ,
8170 Node :: Expr ( expr) if let ExprKind :: Field ( ..) = expr. kind => ControlFlow :: Continue ( ( ) ) ,
8271 Node :: Expr ( expr) if let ExprKind :: AddrOf ( _, Mutability :: Not , _) = expr. kind => {
8372 ControlFlow :: Continue ( ( ) )
84- }
73+ } ,
8574 Node :: Expr ( expr)
8675 if let Some ( fn_did) = fn_def_id ( cx, expr)
87- && match_any_def_paths ( cx, fn_did, & [
88- & paths:: CHILD_ID ,
89- & paths:: CHILD_KILL ,
90- ] ) . is_some ( ) =>
76+ && match_any_def_paths ( cx, fn_did, & [ & paths:: CHILD_ID , & paths:: CHILD_KILL ] )
77+ . is_some ( ) =>
9178 {
9279 ControlFlow :: Continue ( ( ) )
93- }
80+ } ,
9481
9582 // Conservatively assume that all other kinds of nodes call `.wait()` somehow.
9683 _ => ControlFlow :: Break ( ( ) ) ,
9784 }
98- } ) . is_continue ( ) ;
85+ } )
86+ . is_continue ( ) ;
9987
10088 // If it does have a `wait()` call, we're done. Don't lint.
10189 if !has_no_wait {
10290 return ;
10391 }
10492
105- check ( cx, expr, local. hir_id ) ;
93+ // Don't emit a suggestion since the binding is used later
94+ check ( cx, expr, local. hir_id , false ) ;
10695 } ,
10796 Node :: Local ( & Local { pat, hir_id, .. } ) if let PatKind :: Wild = pat. kind => {
10897 // `let _ = child;`, also dropped immediately without `wait()`ing
109- check ( cx, expr, hir_id) ;
110- }
111- Node :: Stmt ( & Stmt { kind : StmtKind :: Semi ( _) , hir_id, .. } ) => {
98+ check ( cx, expr, hir_id, true ) ;
99+ } ,
100+ Node :: Stmt ( & Stmt {
101+ kind : StmtKind :: Semi ( _) ,
102+ hir_id,
103+ ..
104+ } ) => {
112105 // Immediately dropped. E.g. `std::process::Command::new("echo").spawn().unwrap();`
113- check ( cx, expr, hir_id) ;
114- }
115- _ => { }
106+ check ( cx, expr, hir_id, true ) ;
107+ } ,
108+ _ => { } ,
116109 }
117110 }
118111 }
@@ -126,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
126119///
127120/// This checks if the program doesn't unconditionally exit after the spawn expression and that it
128121/// isn't the last statement of the program.
129- fn check < ' tcx > ( cx : & LateContext < ' tcx > , spawn_expr : & ' tcx Expr < ' tcx > , node_id : HirId ) {
122+ fn check < ' tcx > ( cx : & LateContext < ' tcx > , spawn_expr : & ' tcx Expr < ' tcx > , node_id : HirId , emit_suggestion : bool ) {
130123 let Some ( block) = get_enclosing_block ( cx, spawn_expr. hir_id ) else {
131124 return ;
132125 } ;
@@ -148,7 +141,27 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, node_id: Hi
148141 return ;
149142 }
150143
151- emit_lint ( cx, spawn_expr. span ) ;
144+ span_lint_and_then (
145+ cx,
146+ ZOMBIE_PROCESSES ,
147+ spawn_expr. span ,
148+ "spawned process is never `wait()`ed on" ,
149+ |diag| {
150+ if emit_suggestion {
151+ diag. span_suggestion (
152+ spawn_expr. span . shrink_to_hi ( ) ,
153+ "try" ,
154+ ".wait()" ,
155+ Applicability :: MaybeIncorrect ,
156+ ) ;
157+ } else {
158+ diag. note ( "consider calling `.wait()`" ) ;
159+ }
160+
161+ diag. note ( "not doing so might leave behind zombie processes" )
162+ . note ( "see https://doc.rust-lang.org/stable/std/process/struct.Child.html#warning" ) ;
163+ } ,
164+ ) ;
152165}
153166
154167/// The hir id id may either correspond to a `Local` or `Stmt`, depending on how we got here.
@@ -159,8 +172,8 @@ fn is_last_node_in_main(cx: &LateContext<'_>, id: HirId) -> bool {
159172 let body_owner = hir. enclosing_body_owner ( id) ;
160173 let enclosing_body = hir. body ( hir. body_owned_by ( body_owner) ) ;
161174
162- if cx . tcx . opt_def_kind ( body_owner ) == Some ( DefKind :: Fn )
163- && cx . tcx . opt_item_name ( body_owner . to_def_id ( ) ) == Some ( sym :: main )
175+ if let Some ( ( main_def_id , _ ) ) = cx . tcx . entry_fn ( ( ) )
176+ && main_def_id == body_owner . to_def_id ( )
164177 && let ExprKind :: Block ( block, _) = & enclosing_body. value . peel_blocks ( ) . kind
165178 && let [ .., stmt] = block. stmts
166179 {
@@ -173,11 +186,9 @@ fn is_last_node_in_main(cx: &LateContext<'_>, id: HirId) -> bool {
173186
174187/// Checks if the given expression exits the process.
175188fn is_exit_expression ( cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) -> bool {
176- if let Some ( fn_did) = fn_def_id ( cx, expr) {
177- match_any_def_paths ( cx, fn_did, & [ & paths:: EXIT , & paths:: ABORT ] ) . is_some ( )
178- } else {
179- false
180- }
189+ fn_def_id ( cx, expr) . is_some_and ( |fn_did| {
190+ cx. tcx . is_diagnostic_item ( sym:: process_exit, fn_did) || match_def_path ( cx, fn_did, & paths:: ABORT )
191+ } )
181192}
182193
183194#[ derive( Debug ) ]
0 commit comments