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