]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/non_copy_const.rs
Auto merge of #3511 - phansch:remove_allow_doc_markdown, r=phansch
[rust.git] / clippy_lints / src / non_copy_const.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 //! Checks for uses of const which the type is not Freeze (Cell-free).
11 //!
12 //! This lint is **deny** by default.
13
14 use crate::rustc::hir::def::Def;
15 use crate::rustc::hir::*;
16 use crate::rustc::lint::{LateContext, LateLintPass, Lint, LintArray, LintPass};
17 use crate::rustc::ty::adjustment::Adjust;
18 use crate::rustc::ty::{self, TypeFlags};
19 use crate::rustc::{declare_tool_lint, lint_array};
20 use crate::rustc_errors::Applicability;
21 use crate::rustc_typeck::hir_ty_to_ty;
22 use crate::syntax_pos::{Span, DUMMY_SP};
23 use crate::utils::{in_constant, in_macro, is_copy, span_lint_and_then};
24 use std::ptr;
25
26 /// **What it does:** Checks for declaration of `const` items which is interior
27 /// mutable (e.g. contains a `Cell`, `Mutex`, `AtomicXxxx` etc).
28 ///
29 /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e.
30 /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
31 /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
32 /// these types in the first place.
33 ///
34 /// The `const` should better be replaced by a `static` item if a global
35 /// variable is wanted, or replaced by a `const fn` if a constructor is wanted.
36 ///
37 /// **Known problems:** A "non-constant" const item is a legacy way to supply an
38 /// initialized value to downstream `static` items (e.g. the
39 /// `std::sync::ONCE_INIT` constant). In this case the use of `const` is legit,
40 /// and this lint should be suppressed.
41 ///
42 /// **Example:**
43 /// ```rust
44 /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
45 ///
46 /// // Bad.
47 /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
48 /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
49 /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
50 ///
51 /// // Good.
52 /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15);
53 /// STATIC_ATOM.store(9, SeqCst);
54 /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
55 /// ```
56 declare_clippy_lint! {
57     pub DECLARE_INTERIOR_MUTABLE_CONST,
58     correctness,
59     "declaring const with interior mutability"
60 }
61
62 /// **What it does:** Checks if `const` items which is interior mutable (e.g.
63 /// contains a `Cell`, `Mutex`, `AtomicXxxx` etc) has been borrowed directly.
64 ///
65 /// **Why is this bad?** Consts are copied everywhere they are referenced, i.e.
66 /// every time you refer to the const a fresh instance of the `Cell` or `Mutex`
67 /// or `AtomicXxxx` will be created, which defeats the whole purpose of using
68 /// these types in the first place.
69 ///
70 /// The `const` value should be stored inside a `static` item.
71 ///
72 /// **Known problems:** None
73 ///
74 /// **Example:**
75 /// ```rust
76 /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
77 /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12);
78 ///
79 /// // Bad.
80 /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged
81 /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct
82 ///
83 /// // Good.
84 /// static STATIC_ATOM: AtomicUsize = CONST_ATOM;
85 /// STATIC_ATOM.store(9, SeqCst);
86 /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance
87 /// ```
88 declare_clippy_lint! {
89     pub BORROW_INTERIOR_MUTABLE_CONST,
90     correctness,
91     "referencing const with interior mutability"
92 }
93
94 #[derive(Copy, Clone)]
95 enum Source {
96     Item { item: Span },
97     Assoc { item: Span, ty: Span },
98     Expr { expr: Span },
99 }
100
101 impl Source {
102     fn lint(&self) -> (&'static Lint, &'static str, Span) {
103         match self {
104             Source::Item { item } | Source::Assoc { item, .. } => (
105                 DECLARE_INTERIOR_MUTABLE_CONST,
106                 "a const item should never be interior mutable",
107                 *item,
108             ),
109             Source::Expr { expr } => (
110                 BORROW_INTERIOR_MUTABLE_CONST,
111                 "a const item with interior mutability should not be borrowed",
112                 *expr,
113             ),
114         }
115     }
116 }
117
118 fn verify_ty_bound<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, ty: ty::Ty<'tcx>, source: Source) {
119     if ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP) || is_copy(cx, ty) {
120         // an UnsafeCell is !Copy, and an UnsafeCell is also the only type which
121         // is !Freeze, thus if our type is Copy we can be sure it must be Freeze
122         // as well.
123         return;
124     }
125
126     let (lint, msg, span) = source.lint();
127     span_lint_and_then(cx, lint, span, msg, |db| {
128         if in_macro(span) {
129             return; // Don't give suggestions into macros.
130         }
131         match source {
132             Source::Item { .. } => {
133                 let const_kw_span = span.from_inner_byte_pos(0, 5);
134                 db.span_suggestion_with_applicability(
135                     const_kw_span,
136                     "make this a static item",
137                     "static".to_string(),
138                     Applicability::MachineApplicable,
139                 );
140             },
141             Source::Assoc { ty: ty_span, .. } => {
142                 if ty.flags.contains(TypeFlags::HAS_FREE_LOCAL_NAMES) {
143                     db.span_help(ty_span, &format!("consider requiring `{}` to be `Copy`", ty));
144                 }
145             },
146             Source::Expr { .. } => {
147                 db.help("assign this const to a local or static variable, and use the variable here");
148             },
149         }
150     });
151 }
152
153 pub struct NonCopyConst;
154
155 impl LintPass for NonCopyConst {
156     fn get_lints(&self) -> LintArray {
157         lint_array!(DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST)
158     }
159 }
160
161 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for NonCopyConst {
162     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, it: &'tcx Item) {
163         if let ItemKind::Const(hir_ty, ..) = &it.node {
164             let ty = hir_ty_to_ty(cx.tcx, hir_ty);
165             verify_ty_bound(cx, ty, Source::Item { item: it.span });
166         }
167     }
168
169     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, trait_item: &'tcx TraitItem) {
170         if let TraitItemKind::Const(hir_ty, ..) = &trait_item.node {
171             let ty = hir_ty_to_ty(cx.tcx, hir_ty);
172             verify_ty_bound(
173                 cx,
174                 ty,
175                 Source::Assoc {
176                     ty: hir_ty.span,
177                     item: trait_item.span,
178                 },
179             );
180         }
181     }
182
183     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, impl_item: &'tcx ImplItem) {
184         if let ImplItemKind::Const(hir_ty, ..) = &impl_item.node {
185             let item_node_id = cx.tcx.hir().get_parent_node(impl_item.id);
186             let item = cx.tcx.hir().expect_item(item_node_id);
187             // ensure the impl is an inherent impl.
188             if let ItemKind::Impl(_, _, _, _, None, _, _) = item.node {
189                 let ty = hir_ty_to_ty(cx.tcx, hir_ty);
190                 verify_ty_bound(
191                     cx,
192                     ty,
193                     Source::Assoc {
194                         ty: hir_ty.span,
195                         item: impl_item.span,
196                     },
197                 );
198             }
199         }
200     }
201
202     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
203         if let ExprKind::Path(qpath) = &expr.node {
204             // Only lint if we use the const item inside a function.
205             if in_constant(cx, expr.id) {
206                 return;
207             }
208
209             // make sure it is a const item.
210             match cx.tables.qpath_def(qpath, expr.hir_id) {
211                 Def::Const(_) | Def::AssociatedConst(_) => {},
212                 _ => return,
213             };
214
215             // climb up to resolve any field access and explicit referencing.
216             let mut cur_expr = expr;
217             let mut dereferenced_expr = expr;
218             let mut needs_check_adjustment = true;
219             loop {
220                 let parent_id = cx.tcx.hir().get_parent_node(cur_expr.id);
221                 if parent_id == cur_expr.id {
222                     break;
223                 }
224                 if let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find(parent_id) {
225                     match &parent_expr.node {
226                         ExprKind::AddrOf(..) => {
227                             // `&e` => `e` must be referenced
228                             needs_check_adjustment = false;
229                         },
230                         ExprKind::Field(..) => {
231                             dereferenced_expr = parent_expr;
232                             needs_check_adjustment = true;
233                         },
234                         ExprKind::Index(e, _) if ptr::eq(&**e, cur_expr) => {
235                             // `e[i]` => desugared to `*Index::index(&e, i)`,
236                             // meaning `e` must be referenced.
237                             // no need to go further up since a method call is involved now.
238                             needs_check_adjustment = false;
239                             break;
240                         },
241                         ExprKind::Unary(UnDeref, _) => {
242                             // `*e` => desugared to `*Deref::deref(&e)`,
243                             // meaning `e` must be referenced.
244                             // no need to go further up since a method call is involved now.
245                             needs_check_adjustment = false;
246                             break;
247                         },
248                         _ => break,
249                     }
250                     cur_expr = parent_expr;
251                 } else {
252                     break;
253                 }
254             }
255
256             let ty = if needs_check_adjustment {
257                 let adjustments = cx.tables.expr_adjustments(dereferenced_expr);
258                 if let Some(i) = adjustments.iter().position(|adj| match adj.kind {
259                     Adjust::Borrow(_) | Adjust::Deref(_) => true,
260                     _ => false,
261                 }) {
262                     if i == 0 {
263                         cx.tables.expr_ty(dereferenced_expr)
264                     } else {
265                         adjustments[i - 1].target
266                     }
267                 } else {
268                     // No borrow adjustments = the entire const is moved.
269                     return;
270                 }
271             } else {
272                 cx.tables.expr_ty(dereferenced_expr)
273             };
274
275             verify_ty_bound(cx, ty, Source::Expr { expr: expr.span });
276         }
277     }
278 }