]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
1 | use clippy_utils::{ |
2 | diagnostics::span_lint_and_then, | |
3 | get_attr, | |
4 | source::{indent_of, snippet}, | |
5 | }; | |
6 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
7 | use rustc_errors::{Applicability, Diagnostic}; | |
8 | use rustc_hir::{ | |
9 | self as hir, | |
10 | intravisit::{walk_expr, Visitor}, | |
11 | }; | |
12 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
13 | use rustc_middle::ty::{subst::GenericArgKind, Ty, TypeAndMut}; | |
14 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
15 | use rustc_span::{symbol::Ident, Span, DUMMY_SP}; | |
16 | ||
17 | declare_clippy_lint! { | |
18 | /// ### What it does | |
19 | /// | |
20 | /// Searches for elements marked with `#[clippy::significant_drop]` that could be early | |
21 | /// dropped but are in fact dropped at the end of their scopes. In other words, enforces the | |
22 | /// "tightening" of their possible lifetimes. | |
23 | /// | |
24 | /// ### Why is this bad? | |
25 | /// | |
26 | /// Elements marked with `#[clippy::has_significant_drop]` are generally synchronizing | |
27 | /// primitives that manage shared resources, as such, it is desired to release them as soon as | |
28 | /// possible to avoid unnecessary resource contention. | |
29 | /// | |
30 | /// ### Example | |
31 | /// | |
32 | /// ```rust,ignore | |
33 | /// fn main() { | |
34 | /// let lock = some_sync_resource.lock(); | |
35 | /// let owned_rslt = lock.do_stuff_with_resource(); | |
36 | /// // Only `owned_rslt` is needed but `lock` is still held. | |
37 | /// do_heavy_computation_that_takes_time(owned_rslt); | |
38 | /// } | |
39 | /// ``` | |
40 | /// | |
41 | /// Use instead: | |
42 | /// | |
43 | /// ```rust,ignore | |
44 | /// fn main() { | |
45 | /// let owned_rslt = some_sync_resource.lock().do_stuff_with_resource(); | |
46 | /// do_heavy_computation_that_takes_time(owned_rslt); | |
47 | /// } | |
48 | /// ``` | |
49 | #[clippy::version = "1.67.0"] | |
50 | pub SIGNIFICANT_DROP_TIGHTENING, | |
51 | nursery, | |
52 | "Searches for elements marked with `#[clippy::has_significant_drop]` that could be early dropped but are in fact dropped at the end of their scopes" | |
53 | } | |
54 | ||
55 | impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]); | |
56 | ||
57 | #[derive(Default)] | |
58 | pub struct SignificantDropTightening<'tcx> { | |
59 | /// Auxiliary structure used to avoid having to verify the same type multiple times. | |
60 | seen_types: FxHashSet<Ty<'tcx>>, | |
61 | type_cache: FxHashMap<Ty<'tcx>, bool>, | |
62 | } | |
63 | ||
64 | impl<'tcx> SignificantDropTightening<'tcx> { | |
65 | /// Unifies the statements of a block with its return expression. | |
66 | fn all_block_stmts<'ret, 'rslt, 'stmts>( | |
67 | block_stmts: &'stmts [hir::Stmt<'tcx>], | |
68 | dummy_ret_stmt: Option<&'ret hir::Stmt<'tcx>>, | |
69 | ) -> impl Iterator<Item = &'rslt hir::Stmt<'tcx>> | |
70 | where | |
71 | 'ret: 'rslt, | |
72 | 'stmts: 'rslt, | |
73 | { | |
74 | block_stmts.iter().chain(dummy_ret_stmt) | |
75 | } | |
76 | ||
77 | /// Searches for at least one statement that could slow down the release of a significant drop. | |
78 | fn at_least_one_stmt_is_expensive<'stmt>(stmts: impl Iterator<Item = &'stmt hir::Stmt<'tcx>>) -> bool | |
79 | where | |
80 | 'tcx: 'stmt, | |
81 | { | |
82 | for stmt in stmts { | |
83 | match stmt.kind { | |
84 | hir::StmtKind::Expr(expr) if let hir::ExprKind::Path(_) = expr.kind => {} | |
85 | hir::StmtKind::Local(local) if let Some(expr) = local.init | |
86 | && let hir::ExprKind::Path(_) = expr.kind => {}, | |
87 | _ => return true | |
88 | }; | |
89 | } | |
90 | false | |
91 | } | |
92 | ||
93 | /// Verifies if the expression is of type `drop(some_lock_path)` to assert that the temporary | |
94 | /// is already being dropped before the end of its scope. | |
95 | fn has_drop(expr: &'tcx hir::Expr<'_>, init_bind_ident: Ident) -> bool { | |
96 | if let hir::ExprKind::Call(fun, args) = expr.kind | |
97 | && let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind | |
98 | && let [fun_ident, ..] = fun_path.segments | |
99 | && fun_ident.ident.name == rustc_span::sym::drop | |
100 | && let [first_arg, ..] = args | |
101 | && let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &first_arg.kind | |
102 | && let [first_arg_ps, .. ] = arg_path.segments | |
103 | { | |
104 | first_arg_ps.ident == init_bind_ident | |
105 | } | |
106 | else { | |
107 | false | |
108 | } | |
109 | } | |
110 | ||
111 | /// Tries to find types marked with `#[has_significant_drop]` of an expression `expr` that is | |
112 | /// originated from `stmt` and then performs common logic on `sdap`. | |
113 | fn modify_sdap_if_sig_drop_exists( | |
114 | &mut self, | |
115 | cx: &LateContext<'tcx>, | |
116 | expr: &'tcx hir::Expr<'_>, | |
117 | idx: usize, | |
118 | sdap: &mut SigDropAuxParams, | |
119 | stmt: &hir::Stmt<'_>, | |
120 | cb: impl Fn(&mut SigDropAuxParams), | |
121 | ) { | |
122 | let mut sig_drop_finder = SigDropFinder::new(cx, &mut self.seen_types, &mut self.type_cache); | |
123 | sig_drop_finder.visit_expr(expr); | |
124 | if sig_drop_finder.has_sig_drop { | |
125 | cb(sdap); | |
126 | if sdap.number_of_stmts > 0 { | |
127 | sdap.last_use_stmt_idx = idx; | |
128 | sdap.last_use_stmt_span = stmt.span; | |
129 | if let hir::ExprKind::MethodCall(_, _, _, span) = expr.kind { | |
130 | sdap.last_use_method_span = span; | |
131 | } | |
132 | } | |
133 | sdap.number_of_stmts = sdap.number_of_stmts.wrapping_add(1); | |
134 | } | |
135 | } | |
136 | ||
137 | /// Shows generic overall messages as well as specialized messages depending on the usage. | |
138 | fn set_suggestions(cx: &LateContext<'tcx>, block_span: Span, diag: &mut Diagnostic, sdap: &SigDropAuxParams) { | |
139 | match sdap.number_of_stmts { | |
140 | 0 | 1 => {}, | |
141 | 2 => { | |
142 | let indent = " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)); | |
143 | let init_method = snippet(cx, sdap.init_method_span, ".."); | |
144 | let usage_method = snippet(cx, sdap.last_use_method_span, ".."); | |
145 | let stmt = if let Some(last_use_bind_span) = sdap.last_use_bind_span { | |
146 | format!( | |
147 | "\n{indent}let {} = {init_method}.{usage_method};", | |
148 | snippet(cx, last_use_bind_span, ".."), | |
149 | ) | |
150 | } else { | |
151 | format!("\n{indent}{init_method}.{usage_method};") | |
152 | }; | |
153 | diag.span_suggestion_verbose( | |
154 | sdap.init_stmt_span, | |
155 | "merge the temporary construction with its single usage", | |
156 | stmt, | |
157 | Applicability::MaybeIncorrect, | |
158 | ); | |
159 | diag.span_suggestion( | |
160 | sdap.last_use_stmt_span, | |
161 | "remove separated single usage", | |
162 | "", | |
163 | Applicability::MaybeIncorrect, | |
164 | ); | |
165 | }, | |
166 | _ => { | |
167 | diag.span_suggestion( | |
168 | sdap.last_use_stmt_span.shrink_to_hi(), | |
169 | "drop the temporary after the end of its last usage", | |
170 | format!( | |
171 | "\n{}drop({});", | |
172 | " ".repeat(indent_of(cx, sdap.last_use_stmt_span).unwrap_or(0)), | |
173 | sdap.init_bind_ident | |
174 | ), | |
175 | Applicability::MaybeIncorrect, | |
176 | ); | |
177 | }, | |
178 | } | |
179 | diag.note("this might lead to unnecessary resource contention"); | |
180 | diag.span_label( | |
181 | block_span, | |
182 | format!( | |
183 | "temporary `{}` is currently being dropped at the end of its contained scope", | |
184 | sdap.init_bind_ident | |
185 | ), | |
186 | ); | |
187 | } | |
188 | } | |
189 | ||
190 | impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { | |
191 | fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { | |
192 | let dummy_ret_stmt = block.expr.map(|expr| hir::Stmt { | |
193 | hir_id: hir::HirId::INVALID, | |
194 | kind: hir::StmtKind::Expr(expr), | |
195 | span: DUMMY_SP, | |
196 | }); | |
197 | let mut sdap = SigDropAuxParams::default(); | |
198 | for (idx, stmt) in Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).enumerate() { | |
199 | match stmt.kind { | |
200 | hir::StmtKind::Expr(expr) => self.modify_sdap_if_sig_drop_exists( | |
201 | cx, | |
202 | expr, | |
203 | idx, | |
204 | &mut sdap, | |
205 | stmt, | |
206 | |_| {} | |
207 | ), | |
208 | hir::StmtKind::Local(local) if let Some(expr) = local.init => self.modify_sdap_if_sig_drop_exists( | |
209 | cx, | |
210 | expr, | |
211 | idx, | |
212 | &mut sdap, | |
213 | stmt, | |
214 | |local_sdap| { | |
215 | if local_sdap.number_of_stmts == 0 { | |
216 | if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { | |
217 | local_sdap.init_bind_ident = ident; | |
218 | } | |
219 | if let hir::ExprKind::MethodCall(_, local_expr, _, span) = expr.kind { | |
220 | local_sdap.init_method_span = local_expr.span.to(span); | |
221 | } | |
222 | local_sdap.init_stmt_span = stmt.span; | |
223 | } | |
224 | else if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind { | |
225 | local_sdap.last_use_bind_span = Some(ident.span); | |
226 | } | |
227 | } | |
228 | ), | |
229 | hir::StmtKind::Semi(expr) => { | |
230 | if Self::has_drop(expr, sdap.init_bind_ident) { | |
231 | return; | |
232 | } | |
233 | self.modify_sdap_if_sig_drop_exists(cx, expr, idx, &mut sdap, stmt, |_| {}); | |
234 | }, | |
235 | _ => {} | |
236 | }; | |
237 | } | |
238 | ||
239 | let idx = sdap.last_use_stmt_idx.wrapping_add(1); | |
240 | let stmts_after_last_use = Self::all_block_stmts(block.stmts, dummy_ret_stmt.as_ref()).skip(idx); | |
241 | if sdap.number_of_stmts > 1 && Self::at_least_one_stmt_is_expensive(stmts_after_last_use) { | |
242 | span_lint_and_then( | |
243 | cx, | |
244 | SIGNIFICANT_DROP_TIGHTENING, | |
245 | sdap.init_bind_ident.span, | |
246 | "temporary with significant `Drop` can be early dropped", | |
247 | |diag| { | |
248 | Self::set_suggestions(cx, block.span, diag, &sdap); | |
249 | }, | |
250 | ); | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | /// Auxiliary parameters used on each block check. | |
256 | struct SigDropAuxParams { | |
257 | /// The binding or variable that references the initial construction of the type marked with | |
258 | /// `#[has_significant_drop]`. | |
259 | init_bind_ident: Ident, | |
260 | /// Similar to `init_bind_ident` but encompasses the right-hand method call. | |
261 | init_method_span: Span, | |
262 | /// Similar to `init_bind_ident` but encompasses the whole contained statement. | |
263 | init_stmt_span: Span, | |
264 | ||
265 | /// The last visited binding or variable span within a block that had any referenced inner type | |
266 | /// marked with `#[has_significant_drop]`. | |
267 | last_use_bind_span: Option<Span>, | |
268 | /// Index of the last visited statement within a block that had any referenced inner type | |
269 | /// marked with `#[has_significant_drop]`. | |
270 | last_use_stmt_idx: usize, | |
271 | /// Similar to `last_use_bind_span` but encompasses the whole contained statement. | |
272 | last_use_stmt_span: Span, | |
273 | /// Similar to `last_use_bind_span` but encompasses the right-hand method call. | |
274 | last_use_method_span: Span, | |
275 | ||
276 | /// Total number of statements within a block that have any referenced inner type marked with | |
277 | /// `#[has_significant_drop]`. | |
278 | number_of_stmts: usize, | |
279 | } | |
280 | ||
281 | impl Default for SigDropAuxParams { | |
282 | fn default() -> Self { | |
283 | Self { | |
284 | init_bind_ident: Ident::empty(), | |
285 | init_method_span: DUMMY_SP, | |
286 | init_stmt_span: DUMMY_SP, | |
287 | last_use_bind_span: None, | |
288 | last_use_method_span: DUMMY_SP, | |
289 | last_use_stmt_idx: 0, | |
290 | last_use_stmt_span: DUMMY_SP, | |
291 | number_of_stmts: 0, | |
292 | } | |
293 | } | |
294 | } | |
295 | ||
296 | /// Checks the existence of the `#[has_significant_drop]` attribute | |
297 | struct SigDropChecker<'cx, 'sdt, 'tcx> { | |
298 | cx: &'cx LateContext<'tcx>, | |
299 | seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, | |
300 | type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, | |
301 | } | |
302 | ||
303 | impl<'cx, 'sdt, 'tcx> SigDropChecker<'cx, 'sdt, 'tcx> { | |
304 | pub(crate) fn new( | |
305 | cx: &'cx LateContext<'tcx>, | |
306 | seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, | |
307 | type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, | |
308 | ) -> Self { | |
309 | seen_types.clear(); | |
310 | Self { | |
311 | cx, | |
312 | seen_types, | |
313 | type_cache, | |
314 | } | |
315 | } | |
316 | ||
317 | pub(crate) fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>) -> bool { | |
318 | if let Some(adt) = ty.ty_adt_def() { | |
319 | let mut iter = get_attr( | |
320 | self.cx.sess(), | |
321 | self.cx.tcx.get_attrs_unchecked(adt.did()), | |
322 | "has_significant_drop", | |
323 | ); | |
324 | if iter.next().is_some() { | |
325 | return true; | |
326 | } | |
327 | } | |
328 | match ty.kind() { | |
329 | rustc_middle::ty::Adt(a, b) => { | |
330 | for f in a.all_fields() { | |
331 | let ty = f.ty(self.cx.tcx, b); | |
332 | if !self.has_seen_ty(ty) && self.has_sig_drop_attr(ty) { | |
333 | return true; | |
334 | } | |
335 | } | |
336 | for generic_arg in b.iter() { | |
337 | if let GenericArgKind::Type(ty) = generic_arg.unpack() { | |
338 | if self.has_sig_drop_attr(ty) { | |
339 | return true; | |
340 | } | |
341 | } | |
342 | } | |
343 | false | |
344 | }, | |
345 | rustc_middle::ty::Array(ty, _) | |
346 | | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) | |
347 | | rustc_middle::ty::Ref(_, ty, _) | |
348 | | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(*ty), | |
349 | _ => false, | |
350 | } | |
351 | } | |
352 | ||
353 | pub(crate) fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { | |
354 | // The borrow checker prevents us from using something fancier like or_insert_with. | |
355 | if let Some(ty) = self.type_cache.get(&ty) { | |
356 | return *ty; | |
357 | } | |
358 | let value = self.has_sig_drop_attr_uncached(ty); | |
359 | self.type_cache.insert(ty, value); | |
360 | value | |
361 | } | |
362 | ||
363 | fn has_seen_ty(&mut self, ty: Ty<'tcx>) -> bool { | |
364 | !self.seen_types.insert(ty) | |
365 | } | |
366 | } | |
367 | ||
368 | /// Performs recursive calls to find any inner type marked with `#[has_significant_drop]`. | |
369 | struct SigDropFinder<'cx, 'sdt, 'tcx> { | |
370 | cx: &'cx LateContext<'tcx>, | |
371 | has_sig_drop: bool, | |
372 | sig_drop_checker: SigDropChecker<'cx, 'sdt, 'tcx>, | |
373 | } | |
374 | ||
375 | impl<'cx, 'sdt, 'tcx> SigDropFinder<'cx, 'sdt, 'tcx> { | |
376 | fn new( | |
377 | cx: &'cx LateContext<'tcx>, | |
378 | seen_types: &'sdt mut FxHashSet<Ty<'tcx>>, | |
379 | type_cache: &'sdt mut FxHashMap<Ty<'tcx>, bool>, | |
380 | ) -> Self { | |
381 | Self { | |
382 | cx, | |
383 | has_sig_drop: false, | |
384 | sig_drop_checker: SigDropChecker::new(cx, seen_types, type_cache), | |
385 | } | |
386 | } | |
387 | } | |
388 | ||
389 | impl<'cx, 'sdt, 'tcx> Visitor<'tcx> for SigDropFinder<'cx, 'sdt, 'tcx> { | |
390 | fn visit_expr(&mut self, ex: &'tcx hir::Expr<'_>) { | |
391 | if self | |
392 | .sig_drop_checker | |
393 | .has_sig_drop_attr(self.cx.typeck_results().expr_ty(ex)) | |
394 | { | |
395 | self.has_sig_drop = true; | |
396 | return; | |
397 | } | |
398 | ||
399 | match ex.kind { | |
400 | hir::ExprKind::MethodCall(_, expr, ..) => { | |
401 | self.visit_expr(expr); | |
402 | }, | |
403 | hir::ExprKind::Array(..) | |
404 | | hir::ExprKind::Assign(..) | |
405 | | hir::ExprKind::AssignOp(..) | |
406 | | hir::ExprKind::Binary(..) | |
9ffffee4 FG |
407 | | hir::ExprKind::Call(..) |
408 | | hir::ExprKind::Field(..) | |
409 | | hir::ExprKind::If(..) | |
410 | | hir::ExprKind::Index(..) | |
411 | | hir::ExprKind::Match(..) | |
412 | | hir::ExprKind::Repeat(..) | |
413 | | hir::ExprKind::Ret(..) | |
414 | | hir::ExprKind::Tup(..) | |
415 | | hir::ExprKind::Unary(..) | |
416 | | hir::ExprKind::Yield(..) => { | |
417 | walk_expr(self, ex); | |
418 | }, | |
419 | _ => {}, | |
420 | } | |
421 | } | |
422 | } |