]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs
Update upstream source from tag 'upstream/1.70.0+dfsg1'
[rustc.git] / src / tools / clippy / clippy_lints / src / significant_drop_tightening.rs
CommitLineData
9ffffee4
FG
1use clippy_utils::{
2 diagnostics::span_lint_and_then,
3 get_attr,
4 source::{indent_of, snippet},
5};
6use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7use rustc_errors::{Applicability, Diagnostic};
8use rustc_hir::{
9 self as hir,
10 intravisit::{walk_expr, Visitor},
11};
12use rustc_lint::{LateContext, LateLintPass, LintContext};
13use rustc_middle::ty::{subst::GenericArgKind, Ty, TypeAndMut};
14use rustc_session::{declare_tool_lint, impl_lint_pass};
15use rustc_span::{symbol::Ident, Span, DUMMY_SP};
16
17declare_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
55impl_lint_pass!(SignificantDropTightening<'_> => [SIGNIFICANT_DROP_TIGHTENING]);
56
57#[derive(Default)]
58pub 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
64impl<'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
190impl<'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.
256struct 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
281impl 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
297struct 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
303impl<'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]`.
369struct SigDropFinder<'cx, 'sdt, 'tcx> {
370 cx: &'cx LateContext<'tcx>,
371 has_sig_drop: bool,
372 sig_drop_checker: SigDropChecker<'cx, 'sdt, 'tcx>,
373}
374
375impl<'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
389impl<'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}