]>
Commit | Line | Data |
---|---|---|
04454e1e FG |
1 | use crate::FxHashSet; |
2 | use clippy_utils::diagnostics::span_lint_and_then; | |
04454e1e | 3 | use clippy_utils::source::{indent_of, snippet}; |
064997fb | 4 | use clippy_utils::{get_attr, is_lint_allowed}; |
04454e1e FG |
5 | use rustc_errors::{Applicability, Diagnostic}; |
6 | use rustc_hir::intravisit::{walk_expr, Visitor}; | |
064997fb | 7 | use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; |
923072b8 | 8 | use rustc_lint::{LateContext, LintContext}; |
04454e1e FG |
9 | use rustc_middle::ty::subst::GenericArgKind; |
10 | use rustc_middle::ty::{Ty, TypeAndMut}; | |
04454e1e FG |
11 | use rustc_span::Span; |
12 | ||
923072b8 | 13 | use super::SIGNIFICANT_DROP_IN_SCRUTINEE; |
04454e1e | 14 | |
923072b8 FG |
15 | pub(super) fn check<'tcx>( |
16 | cx: &LateContext<'tcx>, | |
17 | expr: &'tcx Expr<'tcx>, | |
18 | scrutinee: &'tcx Expr<'_>, | |
064997fb | 19 | arms: &'tcx [Arm<'_>], |
923072b8 FG |
20 | source: MatchSource, |
21 | ) { | |
064997fb FG |
22 | if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) { |
23 | return; | |
24 | } | |
25 | ||
923072b8 FG |
26 | if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) { |
27 | for found in suggestions { | |
28 | span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { | |
29 | set_diagnostic(diag, cx, expr, found); | |
064997fb FG |
30 | let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); |
31 | diag.span_label(s, "temporary lives until here"); | |
32 | for span in has_significant_drop_in_arms(cx, arms) { | |
33 | diag.span_label(span, "another value with significant `Drop` created here"); | |
34 | } | |
35 | diag.note("this might lead to deadlocks or other unexpected behavior"); | |
923072b8 | 36 | }); |
04454e1e FG |
37 | } |
38 | } | |
39 | } | |
40 | ||
41 | fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { | |
42 | if found.lint_suggestion == LintSuggestion::MoveAndClone { | |
43 | // If our suggestion is to move and clone, then we want to leave it to the user to | |
44 | // decide how to address this lint, since it may be that cloning is inappropriate. | |
45 | // Therefore, we won't to emit a suggestion. | |
46 | return; | |
47 | } | |
48 | ||
49 | let original = snippet(cx, found.found_span, ".."); | |
50 | let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); | |
51 | ||
52 | let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy { | |
2b03887a | 53 | format!("let value = *{original};\n{trailing_indent}") |
04454e1e FG |
54 | } else if found.is_unit_return_val { |
55 | // If the return value of the expression to be moved is unit, then we don't need to | |
56 | // capture the result in a temporary -- we can just replace it completely with `()`. | |
2b03887a | 57 | format!("{original};\n{trailing_indent}") |
04454e1e | 58 | } else { |
2b03887a | 59 | format!("let value = {original};\n{trailing_indent}") |
04454e1e FG |
60 | }; |
61 | ||
62 | let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly { | |
63 | "try moving the temporary above the match" | |
64 | } else { | |
65 | "try moving the temporary above the match and create a copy" | |
66 | }; | |
67 | ||
68 | let scrutinee_replacement = if found.is_unit_return_val { | |
69 | "()".to_owned() | |
70 | } else { | |
71 | "value".to_owned() | |
72 | }; | |
73 | ||
74 | diag.multipart_suggestion( | |
75 | suggestion_message, | |
76 | vec![ | |
77 | (expr.span.shrink_to_lo(), replacement), | |
78 | (found.found_span, scrutinee_replacement), | |
79 | ], | |
80 | Applicability::MaybeIncorrect, | |
81 | ); | |
82 | } | |
83 | ||
923072b8 FG |
84 | /// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that |
85 | /// may have a surprising lifetime. | |
04454e1e FG |
86 | fn has_significant_drop_in_scrutinee<'tcx, 'a>( |
87 | cx: &'a LateContext<'tcx>, | |
923072b8 FG |
88 | scrutinee: &'tcx Expr<'tcx>, |
89 | source: MatchSource, | |
90 | ) -> Option<(Vec<FoundSigDrop>, &'static str)> { | |
04454e1e | 91 | let mut helper = SigDropHelper::new(cx); |
064997fb FG |
92 | let scrutinee = match (source, &scrutinee.kind) { |
93 | (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, | |
94 | _ => scrutinee, | |
95 | }; | |
923072b8 FG |
96 | helper.find_sig_drop(scrutinee).map(|drops| { |
97 | let message = if source == MatchSource::Normal { | |
064997fb | 98 | "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" |
923072b8 | 99 | } else { |
064997fb | 100 | "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" |
923072b8 FG |
101 | }; |
102 | (drops, message) | |
103 | }) | |
04454e1e FG |
104 | } |
105 | ||
064997fb FG |
106 | struct SigDropChecker<'a, 'tcx> { |
107 | seen_types: FxHashSet<Ty<'tcx>>, | |
108 | cx: &'a LateContext<'tcx>, | |
109 | } | |
110 | ||
111 | impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { | |
112 | fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> { | |
113 | SigDropChecker { | |
114 | seen_types: FxHashSet::default(), | |
115 | cx, | |
116 | } | |
117 | } | |
118 | ||
119 | fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> { | |
120 | self.cx.typeck_results().expr_ty(ex) | |
121 | } | |
122 | ||
123 | fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool { | |
124 | !self.seen_types.insert(ty) | |
125 | } | |
126 | ||
127 | fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { | |
128 | if let Some(adt) = ty.ty_adt_def() { | |
129 | if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 { | |
130 | return true; | |
131 | } | |
132 | } | |
133 | ||
134 | match ty.kind() { | |
135 | rustc_middle::ty::Adt(a, b) => { | |
136 | for f in a.all_fields() { | |
137 | let ty = f.ty(cx.tcx, b); | |
138 | if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) { | |
139 | return true; | |
140 | } | |
141 | } | |
142 | ||
143 | for generic_arg in b.iter() { | |
144 | if let GenericArgKind::Type(ty) = generic_arg.unpack() { | |
145 | if self.has_sig_drop_attr(cx, ty) { | |
146 | return true; | |
147 | } | |
148 | } | |
149 | } | |
150 | false | |
151 | }, | |
152 | rustc_middle::ty::Array(ty, _) | |
153 | | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) | |
154 | | rustc_middle::ty::Ref(_, ty, _) | |
155 | | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty), | |
156 | _ => false, | |
157 | } | |
158 | } | |
159 | } | |
160 | ||
04454e1e FG |
161 | struct SigDropHelper<'a, 'tcx> { |
162 | cx: &'a LateContext<'tcx>, | |
163 | is_chain_end: bool, | |
04454e1e FG |
164 | has_significant_drop: bool, |
165 | current_sig_drop: Option<FoundSigDrop>, | |
166 | sig_drop_spans: Option<Vec<FoundSigDrop>>, | |
167 | special_handling_for_binary_op: bool, | |
064997fb | 168 | sig_drop_checker: SigDropChecker<'a, 'tcx>, |
04454e1e FG |
169 | } |
170 | ||
923072b8 | 171 | #[expect(clippy::enum_variant_names)] |
04454e1e FG |
172 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
173 | enum LintSuggestion { | |
174 | MoveOnly, | |
175 | MoveAndDerefToCopy, | |
176 | MoveAndClone, | |
177 | } | |
178 | ||
179 | #[derive(Clone, Copy)] | |
180 | struct FoundSigDrop { | |
181 | found_span: Span, | |
182 | is_unit_return_val: bool, | |
183 | lint_suggestion: LintSuggestion, | |
184 | } | |
185 | ||
186 | impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { | |
187 | fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { | |
188 | SigDropHelper { | |
189 | cx, | |
190 | is_chain_end: true, | |
04454e1e FG |
191 | has_significant_drop: false, |
192 | current_sig_drop: None, | |
193 | sig_drop_spans: None, | |
194 | special_handling_for_binary_op: false, | |
064997fb | 195 | sig_drop_checker: SigDropChecker::new(cx), |
04454e1e FG |
196 | } |
197 | } | |
198 | ||
199 | fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option<Vec<FoundSigDrop>> { | |
200 | self.visit_expr(match_expr); | |
201 | ||
202 | // If sig drop spans is empty but we found a significant drop, it means that we didn't find | |
203 | // a type that was trivially copyable as we moved up the chain after finding a significant | |
204 | // drop, so move the entire scrutinee. | |
205 | if self.has_significant_drop && self.sig_drop_spans.is_none() { | |
206 | self.try_setting_current_suggestion(match_expr, true); | |
207 | self.move_current_suggestion(); | |
208 | } | |
209 | ||
210 | self.sig_drop_spans.take() | |
211 | } | |
212 | ||
923072b8 FG |
213 | fn replace_current_sig_drop( |
214 | &mut self, | |
215 | found_span: Span, | |
216 | is_unit_return_val: bool, | |
217 | lint_suggestion: LintSuggestion, | |
218 | ) { | |
219 | self.current_sig_drop.replace(FoundSigDrop { | |
220 | found_span, | |
221 | is_unit_return_val, | |
222 | lint_suggestion, | |
223 | }); | |
224 | } | |
225 | ||
04454e1e | 226 | /// This will try to set the current suggestion (so it can be moved into the suggestions vec |
923072b8 | 227 | /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us |
04454e1e FG |
228 | /// an opportunity to look for another type in the chain that will be trivially copyable. |
229 | /// However, if we are at the the end of the chain, we want to accept whatever is there. (The | |
230 | /// suggestion won't actually be output, but the diagnostic message will be output, so the user | |
231 | /// can determine the best way to handle the lint.) | |
232 | fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) { | |
233 | if self.current_sig_drop.is_some() { | |
234 | return; | |
235 | } | |
064997fb | 236 | let ty = self.sig_drop_checker.get_type(expr); |
04454e1e FG |
237 | if ty.is_ref() { |
238 | // We checked that the type was ref, so builtin_deref will return Some TypeAndMut, | |
239 | // but let's avoid any chance of an ICE | |
240 | if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) { | |
241 | if ty.is_trivially_pure_clone_copy() { | |
923072b8 | 242 | self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy); |
04454e1e | 243 | } else if allow_move_and_clone { |
923072b8 | 244 | self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); |
04454e1e FG |
245 | } |
246 | } | |
247 | } else if ty.is_trivially_pure_clone_copy() { | |
923072b8 FG |
248 | self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly); |
249 | } else if allow_move_and_clone { | |
250 | self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); | |
04454e1e FG |
251 | } |
252 | } | |
253 | ||
254 | fn move_current_suggestion(&mut self) { | |
255 | if let Some(current) = self.current_sig_drop.take() { | |
256 | self.sig_drop_spans.get_or_insert_with(Vec::new).push(current); | |
257 | } | |
258 | } | |
259 | ||
04454e1e FG |
260 | fn visit_exprs_for_binary_ops( |
261 | &mut self, | |
262 | left: &'tcx Expr<'_>, | |
263 | right: &'tcx Expr<'_>, | |
264 | is_unit_return_val: bool, | |
265 | span: Span, | |
266 | ) { | |
267 | self.special_handling_for_binary_op = true; | |
268 | self.visit_expr(left); | |
269 | self.visit_expr(right); | |
270 | ||
271 | // If either side had a significant drop, suggest moving the entire scrutinee to avoid | |
272 | // unnecessary copies and to simplify cases where both sides have significant drops. | |
273 | if self.has_significant_drop { | |
923072b8 | 274 | self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly); |
04454e1e FG |
275 | } |
276 | ||
277 | self.special_handling_for_binary_op = false; | |
278 | } | |
04454e1e FG |
279 | } |
280 | ||
281 | impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { | |
282 | fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { | |
064997fb FG |
283 | if !self.is_chain_end |
284 | && self | |
285 | .sig_drop_checker | |
286 | .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex)) | |
287 | { | |
04454e1e FG |
288 | self.has_significant_drop = true; |
289 | return; | |
290 | } | |
291 | self.is_chain_end = false; | |
292 | ||
293 | match ex.kind { | |
f2b60f7d | 294 | ExprKind::MethodCall(_, expr, ..) => { |
923072b8 | 295 | self.visit_expr(expr); |
04454e1e FG |
296 | } |
297 | ExprKind::Binary(_, left, right) => { | |
298 | self.visit_exprs_for_binary_ops(left, right, false, ex.span); | |
299 | } | |
923072b8 | 300 | ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => { |
04454e1e FG |
301 | self.visit_exprs_for_binary_ops(left, right, true, ex.span); |
302 | } | |
303 | ExprKind::Tup(exprs) => { | |
304 | for expr in exprs { | |
305 | self.visit_expr(expr); | |
306 | if self.has_significant_drop { | |
307 | // We may have not have set current_sig_drop if all the suggestions were | |
308 | // MoveAndClone, so add this tuple item's full expression in that case. | |
309 | if self.current_sig_drop.is_none() { | |
310 | self.try_setting_current_suggestion(expr, true); | |
311 | } | |
312 | ||
313 | // Now we are guaranteed to have something, so add it to the final vec. | |
314 | self.move_current_suggestion(); | |
315 | } | |
316 | // Reset `has_significant_drop` after each tuple expression so we can look for | |
317 | // additional cases. | |
318 | self.has_significant_drop = false; | |
319 | } | |
320 | if self.sig_drop_spans.is_some() { | |
321 | self.has_significant_drop = true; | |
322 | } | |
323 | } | |
324 | ExprKind::Box(..) | | |
923072b8 FG |
325 | ExprKind::Array(..) | |
326 | ExprKind::Call(..) | | |
327 | ExprKind::Unary(..) | | |
328 | ExprKind::If(..) | | |
329 | ExprKind::Match(..) | | |
330 | ExprKind::Field(..) | | |
331 | ExprKind::Index(..) | | |
332 | ExprKind::Ret(..) | | |
333 | ExprKind::Repeat(..) | | |
f2b60f7d | 334 | ExprKind::Yield(..) => walk_expr(self, ex), |
04454e1e | 335 | ExprKind::AddrOf(_, _, _) | |
923072b8 FG |
336 | ExprKind::Block(_, _) | |
337 | ExprKind::Break(_, _) | | |
338 | ExprKind::Cast(_, _) | | |
339 | // Don't want to check the closure itself, only invocation, which is covered by MethodCall | |
340 | ExprKind::Closure { .. } | | |
341 | ExprKind::ConstBlock(_) | | |
342 | ExprKind::Continue(_) | | |
343 | ExprKind::DropTemps(_) | | |
344 | ExprKind::Err | | |
345 | ExprKind::InlineAsm(_) | | |
346 | ExprKind::Let(_) | | |
347 | ExprKind::Lit(_) | | |
348 | ExprKind::Loop(_, _, _, _) | | |
349 | ExprKind::Path(_) | | |
350 | ExprKind::Struct(_, _, _) | | |
351 | ExprKind::Type(_, _) => { | |
04454e1e FG |
352 | return; |
353 | } | |
354 | } | |
355 | ||
356 | // Once a significant temporary has been found, we need to go back up at least 1 level to | |
357 | // find the span to extract for replacement, so the temporary gets dropped. However, for | |
358 | // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to | |
359 | // simplify cases where both sides have significant drops. | |
360 | if self.has_significant_drop && !self.special_handling_for_binary_op { | |
361 | self.try_setting_current_suggestion(ex, false); | |
362 | } | |
363 | } | |
364 | } | |
064997fb FG |
365 | |
366 | struct ArmSigDropHelper<'a, 'tcx> { | |
367 | sig_drop_checker: SigDropChecker<'a, 'tcx>, | |
368 | found_sig_drop_spans: FxHashSet<Span>, | |
369 | } | |
370 | ||
371 | impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> { | |
372 | fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> { | |
373 | ArmSigDropHelper { | |
374 | sig_drop_checker: SigDropChecker::new(cx), | |
375 | found_sig_drop_spans: FxHashSet::<Span>::default(), | |
376 | } | |
377 | } | |
378 | } | |
379 | ||
380 | fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> { | |
381 | let mut helper = ArmSigDropHelper::new(cx); | |
382 | for arm in arms { | |
383 | helper.visit_expr(arm.body); | |
384 | } | |
385 | helper.found_sig_drop_spans | |
386 | } | |
387 | ||
388 | impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> { | |
389 | fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { | |
390 | if self | |
391 | .sig_drop_checker | |
392 | .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex)) | |
393 | { | |
394 | self.found_sig_drop_spans.insert(ex.span); | |
395 | return; | |
396 | } | |
397 | walk_expr(self, ex); | |
398 | } | |
399 | } |