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