]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then}; |
2 | use clippy_utils::paths; | |
3 | use clippy_utils::ty::{implements_trait, is_copy}; | |
136023e0 | 4 | use clippy_utils::{get_trait_def_id, is_automatically_derived, is_lint_allowed, match_def_path}; |
f20569fa XL |
5 | use if_chain::if_chain; |
6 | use rustc_hir::def_id::DefId; | |
7 | use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, NestedVisitorMap, Visitor}; | |
8 | use rustc_hir::{ | |
9 | BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety, | |
10 | }; | |
11 | use rustc_lint::{LateContext, LateLintPass}; | |
12 | use rustc_middle::hir::map::Map; | |
13 | use rustc_middle::ty::{self, Ty}; | |
14 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
17df50a5 | 15 | use rustc_span::source_map::Span; |
f20569fa XL |
16 | |
17 | declare_clippy_lint! { | |
18 | /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` | |
19 | /// explicitly or vice versa. | |
20 | /// | |
21 | /// **Why is this bad?** The implementation of these traits must agree (for | |
22 | /// example for use with `HashMap`) so it’s probably a bad idea to use a | |
23 | /// default-generated `Hash` implementation with an explicitly defined | |
24 | /// `PartialEq`. In particular, the following must hold for any type: | |
25 | /// | |
26 | /// ```text | |
27 | /// k1 == k2 ⇒ hash(k1) == hash(k2) | |
28 | /// ``` | |
29 | /// | |
30 | /// **Known problems:** None. | |
31 | /// | |
32 | /// **Example:** | |
33 | /// ```ignore | |
34 | /// #[derive(Hash)] | |
35 | /// struct Foo; | |
36 | /// | |
37 | /// impl PartialEq for Foo { | |
38 | /// ... | |
39 | /// } | |
40 | /// ``` | |
41 | pub DERIVE_HASH_XOR_EQ, | |
42 | correctness, | |
43 | "deriving `Hash` but implementing `PartialEq` explicitly" | |
44 | } | |
45 | ||
46 | declare_clippy_lint! { | |
47 | /// **What it does:** Checks for deriving `Ord` but implementing `PartialOrd` | |
48 | /// explicitly or vice versa. | |
49 | /// | |
50 | /// **Why is this bad?** The implementation of these traits must agree (for | |
51 | /// example for use with `sort`) so it’s probably a bad idea to use a | |
52 | /// default-generated `Ord` implementation with an explicitly defined | |
53 | /// `PartialOrd`. In particular, the following must hold for any type | |
54 | /// implementing `Ord`: | |
55 | /// | |
56 | /// ```text | |
57 | /// k1.cmp(&k2) == k1.partial_cmp(&k2).unwrap() | |
58 | /// ``` | |
59 | /// | |
60 | /// **Known problems:** None. | |
61 | /// | |
62 | /// **Example:** | |
63 | /// | |
64 | /// ```rust,ignore | |
65 | /// #[derive(Ord, PartialEq, Eq)] | |
66 | /// struct Foo; | |
67 | /// | |
68 | /// impl PartialOrd for Foo { | |
69 | /// ... | |
70 | /// } | |
71 | /// ``` | |
72 | /// Use instead: | |
73 | /// ```rust,ignore | |
74 | /// #[derive(PartialEq, Eq)] | |
75 | /// struct Foo; | |
76 | /// | |
77 | /// impl PartialOrd for Foo { | |
78 | /// fn partial_cmp(&self, other: &Foo) -> Option<Ordering> { | |
79 | /// Some(self.cmp(other)) | |
80 | /// } | |
81 | /// } | |
82 | /// | |
83 | /// impl Ord for Foo { | |
84 | /// ... | |
85 | /// } | |
86 | /// ``` | |
87 | /// or, if you don't need a custom ordering: | |
88 | /// ```rust,ignore | |
89 | /// #[derive(Ord, PartialOrd, PartialEq, Eq)] | |
90 | /// struct Foo; | |
91 | /// ``` | |
92 | pub DERIVE_ORD_XOR_PARTIAL_ORD, | |
93 | correctness, | |
94 | "deriving `Ord` but implementing `PartialOrd` explicitly" | |
95 | } | |
96 | ||
97 | declare_clippy_lint! { | |
98 | /// **What it does:** Checks for explicit `Clone` implementations for `Copy` | |
99 | /// types. | |
100 | /// | |
101 | /// **Why is this bad?** To avoid surprising behaviour, these traits should | |
102 | /// agree and the behaviour of `Copy` cannot be overridden. In almost all | |
103 | /// situations a `Copy` type should have a `Clone` implementation that does | |
104 | /// nothing more than copy the object, which is what `#[derive(Copy, Clone)]` | |
105 | /// gets you. | |
106 | /// | |
107 | /// **Known problems:** Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925 | |
108 | /// | |
109 | /// **Example:** | |
110 | /// ```rust,ignore | |
111 | /// #[derive(Copy)] | |
112 | /// struct Foo; | |
113 | /// | |
114 | /// impl Clone for Foo { | |
115 | /// // .. | |
116 | /// } | |
117 | /// ``` | |
118 | pub EXPL_IMPL_CLONE_ON_COPY, | |
119 | pedantic, | |
120 | "implementing `Clone` explicitly on `Copy` types" | |
121 | } | |
122 | ||
123 | declare_clippy_lint! { | |
124 | /// **What it does:** Checks for deriving `serde::Deserialize` on a type that | |
125 | /// has methods using `unsafe`. | |
126 | /// | |
127 | /// **Why is this bad?** Deriving `serde::Deserialize` will create a constructor | |
128 | /// that may violate invariants hold by another constructor. | |
129 | /// | |
130 | /// **Known problems:** None. | |
131 | /// | |
132 | /// **Example:** | |
133 | /// | |
134 | /// ```rust,ignore | |
135 | /// use serde::Deserialize; | |
136 | /// | |
137 | /// #[derive(Deserialize)] | |
138 | /// pub struct Foo { | |
139 | /// // .. | |
140 | /// } | |
141 | /// | |
142 | /// impl Foo { | |
143 | /// pub fn new() -> Self { | |
144 | /// // setup here .. | |
145 | /// } | |
146 | /// | |
147 | /// pub unsafe fn parts() -> (&str, &str) { | |
148 | /// // assumes invariants hold | |
149 | /// } | |
150 | /// } | |
151 | /// ``` | |
152 | pub UNSAFE_DERIVE_DESERIALIZE, | |
153 | pedantic, | |
154 | "deriving `serde::Deserialize` on a type that has methods using `unsafe`" | |
155 | } | |
156 | ||
157 | declare_lint_pass!(Derive => [ | |
158 | EXPL_IMPL_CLONE_ON_COPY, | |
159 | DERIVE_HASH_XOR_EQ, | |
160 | DERIVE_ORD_XOR_PARTIAL_ORD, | |
161 | UNSAFE_DERIVE_DESERIALIZE | |
162 | ]); | |
163 | ||
164 | impl<'tcx> LateLintPass<'tcx> for Derive { | |
165 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { | |
166 | if let ItemKind::Impl(Impl { | |
167 | of_trait: Some(ref trait_ref), | |
168 | .. | |
169 | }) = item.kind | |
170 | { | |
171 | let ty = cx.tcx.type_of(item.def_id); | |
172 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
173 | let is_automatically_derived = is_automatically_derived(attrs); | |
174 | ||
175 | check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); | |
176 | check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); | |
177 | ||
178 | if is_automatically_derived { | |
179 | check_unsafe_derive_deserialize(cx, item, trait_ref, ty); | |
180 | } else { | |
181 | check_copy_clone(cx, item, trait_ref, ty); | |
182 | } | |
183 | } | |
184 | } | |
185 | } | |
186 | ||
187 | /// Implementation of the `DERIVE_HASH_XOR_EQ` lint. | |
188 | fn check_hash_peq<'tcx>( | |
189 | cx: &LateContext<'tcx>, | |
190 | span: Span, | |
191 | trait_ref: &TraitRef<'_>, | |
192 | ty: Ty<'tcx>, | |
193 | hash_is_automatically_derived: bool, | |
194 | ) { | |
195 | if_chain! { | |
196 | if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); | |
197 | if let Some(def_id) = trait_ref.trait_def_id(); | |
198 | if match_def_path(cx, def_id, &paths::HASH); | |
199 | then { | |
200 | // Look for the PartialEq implementations for `ty` | |
201 | cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { | |
cdc7bbd5 | 202 | let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); |
f20569fa XL |
203 | |
204 | if peq_is_automatically_derived == hash_is_automatically_derived { | |
205 | return; | |
206 | } | |
207 | ||
208 | let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); | |
209 | ||
210 | // Only care about `impl PartialEq<Foo> for Foo` | |
211 | // For `impl PartialEq<B> for A, input_types is [A, B] | |
212 | if trait_ref.substs.type_at(1) == ty { | |
213 | let mess = if peq_is_automatically_derived { | |
214 | "you are implementing `Hash` explicitly but have derived `PartialEq`" | |
215 | } else { | |
216 | "you are deriving `Hash` but have implemented `PartialEq` explicitly" | |
217 | }; | |
218 | ||
219 | span_lint_and_then( | |
220 | cx, | |
221 | DERIVE_HASH_XOR_EQ, | |
222 | span, | |
223 | mess, | |
224 | |diag| { | |
225 | if let Some(local_def_id) = impl_id.as_local() { | |
226 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); | |
227 | diag.span_note( | |
228 | cx.tcx.hir().span(hir_id), | |
229 | "`PartialEq` implemented here" | |
230 | ); | |
231 | } | |
232 | } | |
233 | ); | |
234 | } | |
235 | }); | |
236 | } | |
237 | } | |
238 | } | |
239 | ||
240 | /// Implementation of the `DERIVE_ORD_XOR_PARTIAL_ORD` lint. | |
241 | fn check_ord_partial_ord<'tcx>( | |
242 | cx: &LateContext<'tcx>, | |
243 | span: Span, | |
244 | trait_ref: &TraitRef<'_>, | |
245 | ty: Ty<'tcx>, | |
246 | ord_is_automatically_derived: bool, | |
247 | ) { | |
248 | if_chain! { | |
249 | if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD); | |
250 | if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait(); | |
251 | if let Some(def_id) = &trait_ref.trait_def_id(); | |
252 | if *def_id == ord_trait_def_id; | |
253 | then { | |
254 | // Look for the PartialOrd implementations for `ty` | |
255 | cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { | |
cdc7bbd5 | 256 | let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); |
f20569fa XL |
257 | |
258 | if partial_ord_is_automatically_derived == ord_is_automatically_derived { | |
259 | return; | |
260 | } | |
261 | ||
262 | let trait_ref = cx.tcx.impl_trait_ref(impl_id).expect("must be a trait implementation"); | |
263 | ||
264 | // Only care about `impl PartialOrd<Foo> for Foo` | |
265 | // For `impl PartialOrd<B> for A, input_types is [A, B] | |
266 | if trait_ref.substs.type_at(1) == ty { | |
267 | let mess = if partial_ord_is_automatically_derived { | |
268 | "you are implementing `Ord` explicitly but have derived `PartialOrd`" | |
269 | } else { | |
270 | "you are deriving `Ord` but have implemented `PartialOrd` explicitly" | |
271 | }; | |
272 | ||
273 | span_lint_and_then( | |
274 | cx, | |
275 | DERIVE_ORD_XOR_PARTIAL_ORD, | |
276 | span, | |
277 | mess, | |
278 | |diag| { | |
279 | if let Some(local_def_id) = impl_id.as_local() { | |
280 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); | |
281 | diag.span_note( | |
282 | cx.tcx.hir().span(hir_id), | |
283 | "`PartialOrd` implemented here" | |
284 | ); | |
285 | } | |
286 | } | |
287 | ); | |
288 | } | |
289 | }); | |
290 | } | |
291 | } | |
292 | } | |
293 | ||
294 | /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. | |
295 | fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { | |
cdc7bbd5 XL |
296 | let clone_id = match cx.tcx.lang_items().clone_trait() { |
297 | Some(id) if trait_ref.trait_def_id() == Some(id) => id, | |
298 | _ => return, | |
299 | }; | |
300 | let copy_id = match cx.tcx.lang_items().copy_trait() { | |
301 | Some(id) => id, | |
302 | None => return, | |
303 | }; | |
304 | let (ty_adt, ty_subs) = match *ty.kind() { | |
305 | // Unions can't derive clone. | |
306 | ty::Adt(adt, subs) if !adt.is_union() => (adt, subs), | |
307 | _ => return, | |
308 | }; | |
309 | // If the current self type doesn't implement Copy (due to generic constraints), search to see if | |
310 | // there's a Copy impl for any instance of the adt. | |
311 | if !is_copy(cx, ty) { | |
312 | if ty_subs.non_erasable_generics().next().is_some() { | |
17df50a5 XL |
313 | let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| { |
314 | impls | |
315 | .iter() | |
316 | .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did)) | |
317 | }); | |
cdc7bbd5 XL |
318 | if !has_copy_impl { |
319 | return; | |
320 | } | |
321 | } else { | |
f20569fa XL |
322 | return; |
323 | } | |
f20569fa | 324 | } |
cdc7bbd5 XL |
325 | // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for |
326 | // this impl. | |
327 | if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) { | |
328 | return; | |
329 | } | |
330 | ||
331 | span_lint_and_note( | |
332 | cx, | |
333 | EXPL_IMPL_CLONE_ON_COPY, | |
334 | item.span, | |
335 | "you are implementing `Clone` explicitly on a `Copy` type", | |
336 | Some(item.span), | |
337 | "consider deriving `Clone` or removing `Copy`", | |
338 | ); | |
f20569fa XL |
339 | } |
340 | ||
341 | /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. | |
342 | fn check_unsafe_derive_deserialize<'tcx>( | |
343 | cx: &LateContext<'tcx>, | |
344 | item: &Item<'_>, | |
345 | trait_ref: &TraitRef<'_>, | |
346 | ty: Ty<'tcx>, | |
347 | ) { | |
348 | fn item_from_def_id<'tcx>(cx: &LateContext<'tcx>, def_id: DefId) -> &'tcx Item<'tcx> { | |
349 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id.expect_local()); | |
350 | cx.tcx.hir().expect_item(hir_id) | |
351 | } | |
352 | ||
353 | fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { | |
354 | let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; | |
355 | walk_item(&mut visitor, item); | |
356 | visitor.has_unsafe | |
357 | } | |
358 | ||
359 | if_chain! { | |
360 | if let Some(trait_def_id) = trait_ref.trait_def_id(); | |
361 | if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE); | |
362 | if let ty::Adt(def, _) = ty.kind(); | |
363 | if let Some(local_def_id) = def.did.as_local(); | |
364 | let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); | |
136023e0 | 365 | if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id); |
f20569fa XL |
366 | if cx.tcx.inherent_impls(def.did) |
367 | .iter() | |
368 | .map(|imp_did| item_from_def_id(cx, *imp_did)) | |
369 | .any(|imp| has_unsafe(cx, imp)); | |
370 | then { | |
371 | span_lint_and_help( | |
372 | cx, | |
373 | UNSAFE_DERIVE_DESERIALIZE, | |
374 | item.span, | |
375 | "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", | |
376 | None, | |
377 | "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html" | |
378 | ); | |
379 | } | |
380 | } | |
381 | } | |
382 | ||
383 | struct UnsafeVisitor<'a, 'tcx> { | |
384 | cx: &'a LateContext<'tcx>, | |
385 | has_unsafe: bool, | |
386 | } | |
387 | ||
388 | impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { | |
389 | type Map = Map<'tcx>; | |
390 | ||
391 | fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, span: Span, id: HirId) { | |
392 | if self.has_unsafe { | |
393 | return; | |
394 | } | |
395 | ||
396 | if_chain! { | |
397 | if let Some(header) = kind.header(); | |
398 | if let Unsafety::Unsafe = header.unsafety; | |
399 | then { | |
400 | self.has_unsafe = true; | |
401 | } | |
402 | } | |
403 | ||
404 | walk_fn(self, kind, decl, body_id, span, id); | |
405 | } | |
406 | ||
407 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
408 | if self.has_unsafe { | |
409 | return; | |
410 | } | |
411 | ||
412 | if let ExprKind::Block(block, _) = expr.kind { | |
136023e0 XL |
413 | if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules { |
414 | self.has_unsafe = true; | |
f20569fa XL |
415 | } |
416 | } | |
417 | ||
418 | walk_expr(self, expr); | |
419 | } | |
420 | ||
421 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
422 | NestedVisitorMap::All(self.cx.tcx.hir()) | |
423 | } | |
424 | } |