@@ -2,12 +2,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
2
2
use clippy_utils:: visitors:: for_each_local_use_after_expr;
3
3
use clippy_utils:: { fn_def_id, get_enclosing_block, match_any_def_paths, match_def_path, paths} ;
4
4
use rustc_ast:: Mutability ;
5
- use rustc_hir :: def :: DefKind ;
5
+ use rustc_errors :: Applicability ;
6
6
use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
7
7
use rustc_hir:: { Expr , ExprKind , HirId , Local , Node , PatKind , Stmt , StmtKind } ;
8
8
use 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;
11
11
use std:: ops:: ControlFlow ;
12
12
13
13
declare_clippy_lint ! {
@@ -41,19 +41,6 @@ declare_clippy_lint! {
41
41
}
42
42
declare_lint_pass ! ( ZombieProcesses => [ ZOMBIE_PROCESSES ] ) ;
43
43
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
-
57
44
impl < ' tcx > LateLintPass < ' tcx > for ZombieProcesses {
58
45
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
59
46
if let ExprKind :: Call ( ..) | ExprKind :: MethodCall ( ..) = expr. kind
@@ -62,7 +49,6 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
62
49
{
63
50
match cx. tcx . hir ( ) . get_parent ( expr. hir_id ) {
64
51
Node :: Local ( local) if let PatKind :: Binding ( _, local_id, ..) = local. pat . kind => {
65
-
66
52
// If the `Child` is assigned to a variable, we want to check if the code never calls `.wait()`
67
53
// on the variable, and lint if not.
68
54
// This is difficult to do because expressions can be arbitrarily complex
@@ -73,46 +59,53 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
73
59
// - calling `id` or `kill`
74
60
// - no use at all (e.g. `let _x = child;`)
75
61
// - 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
77
63
// Doing it like this means more FNs, but FNs are better than FPs.
78
64
let has_no_wait = for_each_local_use_after_expr ( cx, local_id, expr. hir_id , |expr| {
79
65
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 ( ( ) ) ,
81
70
Node :: Expr ( expr) if let ExprKind :: Field ( ..) = expr. kind => ControlFlow :: Continue ( ( ) ) ,
82
71
Node :: Expr ( expr) if let ExprKind :: AddrOf ( _, Mutability :: Not , _) = expr. kind => {
83
72
ControlFlow :: Continue ( ( ) )
84
- }
73
+ } ,
85
74
Node :: Expr ( expr)
86
75
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 ( ) =>
91
78
{
92
79
ControlFlow :: Continue ( ( ) )
93
- }
80
+ } ,
94
81
95
82
// Conservatively assume that all other kinds of nodes call `.wait()` somehow.
96
83
_ => ControlFlow :: Break ( ( ) ) ,
97
84
}
98
- } ) . is_continue ( ) ;
85
+ } )
86
+ . is_continue ( ) ;
99
87
100
88
// If it does have a `wait()` call, we're done. Don't lint.
101
89
if !has_no_wait {
102
90
return ;
103
91
}
104
92
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 ) ;
106
95
} ,
107
96
Node :: Local ( & Local { pat, hir_id, .. } ) if let PatKind :: Wild = pat. kind => {
108
97
// `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
+ } ) => {
112
105
// 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
+ _ => { } ,
116
109
}
117
110
}
118
111
}
@@ -126,7 +119,7 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses {
126
119
///
127
120
/// This checks if the program doesn't unconditionally exit after the spawn expression and that it
128
121
/// 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 ) {
130
123
let Some ( block) = get_enclosing_block ( cx, spawn_expr. hir_id ) else {
131
124
return ;
132
125
} ;
@@ -148,7 +141,27 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, node_id: Hi
148
141
return ;
149
142
}
150
143
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
+ ) ;
152
165
}
153
166
154
167
/// 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 {
159
172
let body_owner = hir. enclosing_body_owner ( id) ;
160
173
let enclosing_body = hir. body ( hir. body_owned_by ( body_owner) ) ;
161
174
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 ( )
164
177
&& let ExprKind :: Block ( block, _) = & enclosing_body. value . peel_blocks ( ) . kind
165
178
&& let [ .., stmt] = block. stmts
166
179
{
@@ -173,11 +186,9 @@ fn is_last_node_in_main(cx: &LateContext<'_>, id: HirId) -> bool {
173
186
174
187
/// Checks if the given expression exits the process.
175
188
fn 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
+ } )
181
192
}
182
193
183
194
#[ derive( Debug ) ]
0 commit comments