]>
Commit | Line | Data |
---|---|---|
dfeec247 | 1 | use crate::{LateContext, LateLintPass, LintContext}; |
3dfed10e | 2 | use rustc_ast as ast; |
74b04a01 | 3 | use rustc_attr as attr; |
dfeec247 XL |
4 | use rustc_data_structures::fx::FxHashSet; |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir as hir; | |
94222f64 XL |
7 | use rustc_hir::def_id::DefId; |
8 | use rustc_hir::{is_range_literal, Expr, ExprKind, Node}; | |
ba9703b0 XL |
9 | use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton}; |
10 | use rustc_middle::ty::subst::SubstsRef; | |
94222f64 | 11 | use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable}; |
dfeec247 XL |
12 | use rustc_span::source_map; |
13 | use rustc_span::symbol::sym; | |
94222f64 | 14 | use rustc_span::{Span, Symbol, DUMMY_SP}; |
3dfed10e | 15 | use rustc_target::abi::Abi; |
17df50a5 | 16 | use rustc_target::abi::{Integer, LayoutOf, TagEncoding, Variants}; |
3dfed10e | 17 | use rustc_target::spec::abi::Abi as SpecAbi; |
9fa01778 | 18 | |
94222f64 | 19 | use if_chain::if_chain; |
dfeec247 | 20 | use std::cmp; |
cdc7bbd5 | 21 | use std::iter; |
29967ef6 | 22 | use std::ops::ControlFlow; |
3dfed10e | 23 | use tracing::debug; |
9fa01778 | 24 | |
b039eaaf | 25 | declare_lint! { |
1b1a35ee XL |
26 | /// The `unused_comparisons` lint detects comparisons made useless by |
27 | /// limits of the types involved. | |
28 | /// | |
29 | /// ### Example | |
30 | /// | |
31 | /// ```rust | |
32 | /// fn foo(x: u8) { | |
33 | /// x >= 0; | |
34 | /// } | |
35 | /// ``` | |
36 | /// | |
37 | /// {{produces}} | |
38 | /// | |
39 | /// ### Explanation | |
40 | /// | |
41 | /// A useless comparison may indicate a mistake, and should be fixed or | |
42 | /// removed. | |
b039eaaf SL |
43 | UNUSED_COMPARISONS, |
44 | Warn, | |
45 | "comparisons made useless by limits of the types involved" | |
46 | } | |
47 | ||
48 | declare_lint! { | |
1b1a35ee XL |
49 | /// The `overflowing_literals` lint detects literal out of range for its |
50 | /// type. | |
51 | /// | |
52 | /// ### Example | |
53 | /// | |
54 | /// ```rust,compile_fail | |
55 | /// let x: u8 = 1000; | |
56 | /// ``` | |
57 | /// | |
58 | /// {{produces}} | |
59 | /// | |
60 | /// ### Explanation | |
61 | /// | |
62 | /// It is usually a mistake to use a literal that overflows the type where | |
63 | /// it is used. Either use a literal that is within range, or change the | |
64 | /// type to be within the range of the literal. | |
b039eaaf | 65 | OVERFLOWING_LITERALS, |
9fa01778 XL |
66 | Deny, |
67 | "literal out of range for its type" | |
b039eaaf SL |
68 | } |
69 | ||
5bcae85e | 70 | declare_lint! { |
1b1a35ee XL |
71 | /// The `variant_size_differences` lint detects enums with widely varying |
72 | /// variant sizes. | |
73 | /// | |
74 | /// ### Example | |
75 | /// | |
76 | /// ```rust,compile_fail | |
77 | /// #![deny(variant_size_differences)] | |
78 | /// enum En { | |
79 | /// V0(u8), | |
80 | /// VBig([u8; 1024]), | |
81 | /// } | |
82 | /// ``` | |
83 | /// | |
84 | /// {{produces}} | |
85 | /// | |
86 | /// ### Explanation | |
87 | /// | |
88 | /// It can be a mistake to add a variant to an enum that is much larger | |
89 | /// than the other variants, bloating the overall size required for all | |
90 | /// variants. This can impact performance and memory usage. This is | |
91 | /// triggered if one variant is more than 3 times larger than the | |
92 | /// second-largest variant. | |
93 | /// | |
94 | /// Consider placing the large variant's contents on the heap (for example | |
95 | /// via [`Box`]) to keep the overall size of the enum itself down. | |
96 | /// | |
97 | /// This lint is "allow" by default because it can be noisy, and may not be | |
98 | /// an actual problem. Decisions about this should be guided with | |
99 | /// profiling and benchmarking. | |
100 | /// | |
101 | /// [`Box`]: https://doc.rust-lang.org/std/boxed/index.html | |
5bcae85e SL |
102 | VARIANT_SIZE_DIFFERENCES, |
103 | Allow, | |
104 | "detects enums with widely varying variant sizes" | |
105 | } | |
106 | ||
b039eaaf SL |
107 | #[derive(Copy, Clone)] |
108 | pub struct TypeLimits { | |
109 | /// Id of the last visited negated expression | |
ba9703b0 | 110 | negated_expr_id: Option<hir::HirId>, |
b039eaaf SL |
111 | } |
112 | ||
532ac7d7 XL |
113 | impl_lint_pass!(TypeLimits => [UNUSED_COMPARISONS, OVERFLOWING_LITERALS]); |
114 | ||
b039eaaf SL |
115 | impl TypeLimits { |
116 | pub fn new() -> TypeLimits { | |
ba9703b0 | 117 | TypeLimits { negated_expr_id: None } |
b039eaaf SL |
118 | } |
119 | } | |
120 | ||
48663c56 XL |
121 | /// Attempts to special-case the overflowing literal lint when it occurs as a range endpoint. |
122 | /// Returns `true` iff the lint was overridden. | |
f035d41b XL |
123 | fn lint_overflowing_range_endpoint<'tcx>( |
124 | cx: &LateContext<'tcx>, | |
48663c56 XL |
125 | lit: &hir::Lit, |
126 | lit_val: u128, | |
127 | max: u128, | |
dfeec247 XL |
128 | expr: &'tcx hir::Expr<'tcx>, |
129 | parent_expr: &'tcx hir::Expr<'tcx>, | |
60c5eb7d | 130 | ty: &str, |
48663c56 XL |
131 | ) -> bool { |
132 | // We only want to handle exclusive (`..`) ranges, | |
133 | // which are represented as `ExprKind::Struct`. | |
74b04a01 | 134 | let mut overwritten = false; |
e74abb32 | 135 | if let ExprKind::Struct(_, eps, _) = &parent_expr.kind { |
416331ca XL |
136 | if eps.len() != 2 { |
137 | return false; | |
138 | } | |
48663c56 XL |
139 | // We can suggest using an inclusive range |
140 | // (`..=`) instead only if it is the `end` that is | |
141 | // overflowing and only by 1. | |
142 | if eps[1].expr.hir_id == expr.hir_id && lit_val - 1 == max { | |
74b04a01 XL |
143 | cx.struct_span_lint(OVERFLOWING_LITERALS, parent_expr.span, |lint| { |
144 | let mut err = lint.build(&format!("range endpoint is out of range for `{}`", ty)); | |
145 | if let Ok(start) = cx.sess().source_map().span_to_snippet(eps[0].span) { | |
146 | use ast::{LitIntType, LitKind}; | |
147 | // We need to preserve the literal's suffix, | |
148 | // as it may determine typing information. | |
149 | let suffix = match lit.node { | |
29967ef6 XL |
150 | LitKind::Int(_, LitIntType::Signed(s)) => s.name_str(), |
151 | LitKind::Int(_, LitIntType::Unsigned(s)) => s.name_str(), | |
152 | LitKind::Int(_, LitIntType::Unsuffixed) => "", | |
74b04a01 XL |
153 | _ => bug!(), |
154 | }; | |
155 | let suggestion = format!("{}..={}{}", start, lit_val - 1, suffix); | |
156 | err.span_suggestion( | |
157 | parent_expr.span, | |
158 | &"use an inclusive range instead", | |
159 | suggestion, | |
160 | Applicability::MachineApplicable, | |
161 | ); | |
162 | err.emit(); | |
163 | overwritten = true; | |
164 | } | |
165 | }); | |
48663c56 XL |
166 | } |
167 | } | |
74b04a01 | 168 | overwritten |
48663c56 XL |
169 | } |
170 | ||
171 | // For `isize` & `usize`, be conservative with the warnings, so that the | |
172 | // warnings are consistent between 32- and 64-bit platforms. | |
5869c6ff | 173 | fn int_ty_range(int_ty: ty::IntTy) -> (i128, i128) { |
48663c56 | 174 | match int_ty { |
5869c6ff XL |
175 | ty::IntTy::Isize => (i64::MIN.into(), i64::MAX.into()), |
176 | ty::IntTy::I8 => (i8::MIN.into(), i8::MAX.into()), | |
177 | ty::IntTy::I16 => (i16::MIN.into(), i16::MAX.into()), | |
178 | ty::IntTy::I32 => (i32::MIN.into(), i32::MAX.into()), | |
179 | ty::IntTy::I64 => (i64::MIN.into(), i64::MAX.into()), | |
180 | ty::IntTy::I128 => (i128::MIN, i128::MAX), | |
48663c56 XL |
181 | } |
182 | } | |
183 | ||
5869c6ff | 184 | fn uint_ty_range(uint_ty: ty::UintTy) -> (u128, u128) { |
29967ef6 | 185 | let max = match uint_ty { |
5869c6ff XL |
186 | ty::UintTy::Usize => u64::MAX.into(), |
187 | ty::UintTy::U8 => u8::MAX.into(), | |
188 | ty::UintTy::U16 => u16::MAX.into(), | |
189 | ty::UintTy::U32 => u32::MAX.into(), | |
190 | ty::UintTy::U64 => u64::MAX.into(), | |
191 | ty::UintTy::U128 => u128::MAX, | |
29967ef6 XL |
192 | }; |
193 | (0, max) | |
48663c56 XL |
194 | } |
195 | ||
f035d41b | 196 | fn get_bin_hex_repr(cx: &LateContext<'_>, lit: &hir::Lit) -> Option<String> { |
48663c56 XL |
197 | let src = cx.sess().source_map().span_to_snippet(lit.span).ok()?; |
198 | let firstch = src.chars().next()?; | |
199 | ||
200 | if firstch == '0' { | |
201 | match src.chars().nth(1) { | |
ba9703b0 | 202 | Some('x' | 'b') => return Some(src), |
48663c56 XL |
203 | _ => return None, |
204 | } | |
205 | } | |
206 | ||
207 | None | |
208 | } | |
209 | ||
210 | fn report_bin_hex_error( | |
f035d41b | 211 | cx: &LateContext<'_>, |
dfeec247 | 212 | expr: &hir::Expr<'_>, |
48663c56 XL |
213 | ty: attr::IntType, |
214 | repr_str: String, | |
215 | val: u128, | |
216 | negative: bool, | |
217 | ) { | |
ba9703b0 | 218 | let size = Integer::from_attr(&cx.tcx, ty).size(); |
74b04a01 XL |
219 | cx.struct_span_lint(OVERFLOWING_LITERALS, expr.span, |lint| { |
220 | let (t, actually) = match ty { | |
221 | attr::IntType::SignedInt(t) => { | |
6a06907d XL |
222 | let actually = if negative { |
223 | -(size.sign_extend(val) as i128) | |
224 | } else { | |
225 | size.sign_extend(val) as i128 | |
226 | }; | |
74b04a01 XL |
227 | (t.name_str(), actually.to_string()) |
228 | } | |
229 | attr::IntType::UnsignedInt(t) => { | |
29967ef6 | 230 | let actually = size.truncate(val); |
74b04a01 XL |
231 | (t.name_str(), actually.to_string()) |
232 | } | |
233 | }; | |
6a06907d XL |
234 | let mut err = lint.build(&format!("literal out of range for `{}`", t)); |
235 | if negative { | |
236 | // If the value is negative, | |
237 | // emits a note about the value itself, apart from the literal. | |
238 | err.note(&format!( | |
239 | "the literal `{}` (decimal `{}`) does not fit into \ | |
240 | the type `{}`", | |
241 | repr_str, val, t | |
242 | )); | |
243 | err.note(&format!("and the value `-{}` will become `{}{}`", repr_str, actually, t)); | |
244 | } else { | |
245 | err.note(&format!( | |
246 | "the literal `{}` (decimal `{}`) does not fit into \ | |
247 | the type `{}` and will become `{}{}`", | |
248 | repr_str, val, t, actually, t | |
249 | )); | |
250 | } | |
f035d41b | 251 | if let Some(sugg_ty) = |
3dfed10e | 252 | get_type_suggestion(&cx.typeck_results().node_type(expr.hir_id), val, negative) |
74b04a01 XL |
253 | { |
254 | if let Some(pos) = repr_str.chars().position(|c| c == 'i' || c == 'u') { | |
255 | let (sans_suffix, _) = repr_str.split_at(pos); | |
256 | err.span_suggestion( | |
257 | expr.span, | |
6a06907d | 258 | &format!("consider using the type `{}` instead", sugg_ty), |
74b04a01 XL |
259 | format!("{}{}", sans_suffix, sugg_ty), |
260 | Applicability::MachineApplicable, | |
261 | ); | |
262 | } else { | |
6a06907d | 263 | err.help(&format!("consider using the type `{}` instead", sugg_ty)); |
74b04a01 | 264 | } |
48663c56 | 265 | } |
74b04a01 XL |
266 | err.emit(); |
267 | }); | |
48663c56 XL |
268 | } |
269 | ||
270 | // This function finds the next fitting type and generates a suggestion string. | |
271 | // It searches for fitting types in the following way (`X < Y`): | |
272 | // - `iX`: if literal fits in `uX` => `uX`, else => `iY` | |
273 | // - `-iX` => `iY` | |
274 | // - `uX` => `uY` | |
275 | // | |
276 | // No suggestion for: `isize`, `usize`. | |
60c5eb7d | 277 | fn get_type_suggestion(t: Ty<'_>, val: u128, negative: bool) -> Option<&'static str> { |
5869c6ff XL |
278 | use ty::IntTy::*; |
279 | use ty::UintTy::*; | |
48663c56 XL |
280 | macro_rules! find_fit { |
281 | ($ty:expr, $val:expr, $negative:expr, | |
282 | $($type:ident => [$($utypes:expr),*] => [$($itypes:expr),*]),+) => { | |
283 | { | |
284 | let _neg = if negative { 1 } else { 0 }; | |
285 | match $ty { | |
286 | $($type => { | |
287 | $(if !negative && val <= uint_ty_range($utypes).1 { | |
60c5eb7d | 288 | return Some($utypes.name_str()) |
48663c56 XL |
289 | })* |
290 | $(if val <= int_ty_range($itypes).1 as u128 + _neg { | |
60c5eb7d | 291 | return Some($itypes.name_str()) |
48663c56 XL |
292 | })* |
293 | None | |
dc9dc135 | 294 | },)+ |
48663c56 XL |
295 | _ => None |
296 | } | |
297 | } | |
298 | } | |
299 | } | |
1b1a35ee | 300 | match t.kind() { |
48663c56 XL |
301 | ty::Int(i) => find_fit!(i, val, negative, |
302 | I8 => [U8] => [I16, I32, I64, I128], | |
303 | I16 => [U16] => [I32, I64, I128], | |
304 | I32 => [U32] => [I64, I128], | |
305 | I64 => [U64] => [I128], | |
306 | I128 => [U128] => []), | |
307 | ty::Uint(u) => find_fit!(u, val, negative, | |
308 | U8 => [U8, U16, U32, U64, U128] => [], | |
309 | U16 => [U16, U32, U64, U128] => [], | |
310 | U32 => [U32, U64, U128] => [], | |
311 | U64 => [U64, U128] => [], | |
312 | U128 => [U128] => []), | |
313 | _ => None, | |
314 | } | |
315 | } | |
316 | ||
f035d41b XL |
317 | fn lint_int_literal<'tcx>( |
318 | cx: &LateContext<'tcx>, | |
48663c56 | 319 | type_limits: &TypeLimits, |
dfeec247 | 320 | e: &'tcx hir::Expr<'tcx>, |
48663c56 | 321 | lit: &hir::Lit, |
5869c6ff | 322 | t: ty::IntTy, |
48663c56 XL |
323 | v: u128, |
324 | ) { | |
29967ef6 | 325 | let int_type = t.normalize(cx.sess().target.pointer_width); |
ba9703b0 | 326 | let (min, max) = int_ty_range(int_type); |
48663c56 | 327 | let max = max as u128; |
ba9703b0 | 328 | let negative = type_limits.negated_expr_id == Some(e.hir_id); |
48663c56 XL |
329 | |
330 | // Detect literal value out of range [min, max] inclusive | |
331 | // avoiding use of -min to prevent overflow/panic | |
332 | if (negative && v > max + 1) || (!negative && v > max) { | |
333 | if let Some(repr_str) = get_bin_hex_repr(cx, lit) { | |
5869c6ff XL |
334 | report_bin_hex_error( |
335 | cx, | |
336 | e, | |
337 | attr::IntType::SignedInt(ty::ast_int_ty(t)), | |
338 | repr_str, | |
339 | v, | |
340 | negative, | |
341 | ); | |
48663c56 XL |
342 | return; |
343 | } | |
344 | ||
dc9dc135 XL |
345 | let par_id = cx.tcx.hir().get_parent_node(e.hir_id); |
346 | if let Node::Expr(par_e) = cx.tcx.hir().get(par_id) { | |
e74abb32 | 347 | if let hir::ExprKind::Struct(..) = par_e.kind { |
3dfed10e | 348 | if is_range_literal(par_e) |
60c5eb7d | 349 | && lint_overflowing_range_endpoint(cx, lit, v, max, e, par_e, t.name_str()) |
48663c56 XL |
350 | { |
351 | // The overflowing literal lint was overridden. | |
352 | return; | |
353 | } | |
354 | } | |
355 | } | |
356 | ||
74b04a01 | 357 | cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { |
6a06907d XL |
358 | let mut err = lint.build(&format!("literal out of range for `{}`", t.name_str())); |
359 | err.note(&format!( | |
360 | "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`", | |
361 | cx.sess() | |
362 | .source_map() | |
363 | .span_to_snippet(lit.span) | |
364 | .expect("must get snippet from literal"), | |
365 | t.name_str(), | |
366 | min, | |
367 | max, | |
368 | )); | |
369 | if let Some(sugg_ty) = | |
370 | get_type_suggestion(&cx.typeck_results().node_type(e.hir_id), v, negative) | |
371 | { | |
372 | err.help(&format!("consider using the type `{}` instead", sugg_ty)); | |
373 | } | |
374 | err.emit(); | |
74b04a01 | 375 | }); |
48663c56 XL |
376 | } |
377 | } | |
378 | ||
f035d41b XL |
379 | fn lint_uint_literal<'tcx>( |
380 | cx: &LateContext<'tcx>, | |
dfeec247 | 381 | e: &'tcx hir::Expr<'tcx>, |
48663c56 | 382 | lit: &hir::Lit, |
5869c6ff | 383 | t: ty::UintTy, |
48663c56 | 384 | ) { |
29967ef6 | 385 | let uint_type = t.normalize(cx.sess().target.pointer_width); |
48663c56 XL |
386 | let (min, max) = uint_ty_range(uint_type); |
387 | let lit_val: u128 = match lit.node { | |
388 | // _v is u8, within range by definition | |
389 | ast::LitKind::Byte(_v) => return, | |
390 | ast::LitKind::Int(v, _) => v, | |
391 | _ => bug!(), | |
392 | }; | |
393 | if lit_val < min || lit_val > max { | |
dc9dc135 XL |
394 | let parent_id = cx.tcx.hir().get_parent_node(e.hir_id); |
395 | if let Node::Expr(par_e) = cx.tcx.hir().get(parent_id) { | |
e74abb32 | 396 | match par_e.kind { |
48663c56 | 397 | hir::ExprKind::Cast(..) => { |
1b1a35ee | 398 | if let ty::Char = cx.typeck_results().expr_ty(par_e).kind() { |
74b04a01 XL |
399 | cx.struct_span_lint(OVERFLOWING_LITERALS, par_e.span, |lint| { |
400 | lint.build("only `u8` can be cast into `char`") | |
401 | .span_suggestion( | |
402 | par_e.span, | |
403 | &"use a `char` literal instead", | |
404 | format!("'\\u{{{:X}}}'", lit_val), | |
405 | Applicability::MachineApplicable, | |
406 | ) | |
407 | .emit(); | |
408 | }); | |
48663c56 XL |
409 | return; |
410 | } | |
411 | } | |
3dfed10e | 412 | hir::ExprKind::Struct(..) if is_range_literal(par_e) => { |
dfeec247 XL |
413 | let t = t.name_str(); |
414 | if lint_overflowing_range_endpoint(cx, lit, lit_val, max, e, par_e, t) { | |
415 | // The overflowing literal lint was overridden. | |
416 | return; | |
48663c56 | 417 | } |
dfeec247 | 418 | } |
48663c56 XL |
419 | _ => {} |
420 | } | |
421 | } | |
422 | if let Some(repr_str) = get_bin_hex_repr(cx, lit) { | |
5869c6ff XL |
423 | report_bin_hex_error( |
424 | cx, | |
425 | e, | |
426 | attr::IntType::UnsignedInt(ty::ast_uint_ty(t)), | |
427 | repr_str, | |
428 | lit_val, | |
429 | false, | |
430 | ); | |
48663c56 XL |
431 | return; |
432 | } | |
74b04a01 | 433 | cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { |
ba9703b0 XL |
434 | lint.build(&format!("literal out of range for `{}`", t.name_str())) |
435 | .note(&format!( | |
436 | "the literal `{}` does not fit into the type `{}` whose range is `{}..={}`", | |
437 | cx.sess() | |
438 | .source_map() | |
439 | .span_to_snippet(lit.span) | |
440 | .expect("must get snippet from literal"), | |
441 | t.name_str(), | |
442 | min, | |
443 | max, | |
444 | )) | |
445 | .emit() | |
74b04a01 | 446 | }); |
48663c56 XL |
447 | } |
448 | } | |
449 | ||
f035d41b XL |
450 | fn lint_literal<'tcx>( |
451 | cx: &LateContext<'tcx>, | |
48663c56 | 452 | type_limits: &TypeLimits, |
dfeec247 | 453 | e: &'tcx hir::Expr<'tcx>, |
48663c56 XL |
454 | lit: &hir::Lit, |
455 | ) { | |
1b1a35ee | 456 | match *cx.typeck_results().node_type(e.hir_id).kind() { |
48663c56 XL |
457 | ty::Int(t) => { |
458 | match lit.node { | |
ba9703b0 | 459 | ast::LitKind::Int(v, ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed) => { |
48663c56 XL |
460 | lint_int_literal(cx, type_limits, e, lit, t, v) |
461 | } | |
462 | _ => bug!(), | |
463 | }; | |
464 | } | |
dfeec247 | 465 | ty::Uint(t) => lint_uint_literal(cx, e, lit, t), |
48663c56 XL |
466 | ty::Float(t) => { |
467 | let is_infinite = match lit.node { | |
dfeec247 | 468 | ast::LitKind::Float(v, _) => match t { |
5869c6ff XL |
469 | ty::FloatTy::F32 => v.as_str().parse().map(f32::is_infinite), |
470 | ty::FloatTy::F64 => v.as_str().parse().map(f64::is_infinite), | |
dfeec247 | 471 | }, |
48663c56 XL |
472 | _ => bug!(), |
473 | }; | |
474 | if is_infinite == Ok(true) { | |
74b04a01 | 475 | cx.struct_span_lint(OVERFLOWING_LITERALS, e.span, |lint| { |
ba9703b0 XL |
476 | lint.build(&format!("literal out of range for `{}`", t.name_str())) |
477 | .note(&format!( | |
29967ef6 | 478 | "the literal `{}` does not fit into the type `{}` and will be converted to `{}::INFINITY`", |
ba9703b0 XL |
479 | cx.sess() |
480 | .source_map() | |
481 | .span_to_snippet(lit.span) | |
482 | .expect("must get snippet from literal"), | |
483 | t.name_str(), | |
484 | t.name_str(), | |
485 | )) | |
486 | .emit(); | |
74b04a01 | 487 | }); |
48663c56 XL |
488 | } |
489 | } | |
490 | _ => {} | |
491 | } | |
492 | } | |
493 | ||
f035d41b XL |
494 | impl<'tcx> LateLintPass<'tcx> for TypeLimits { |
495 | fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx hir::Expr<'tcx>) { | |
e74abb32 | 496 | match e.kind { |
6a06907d | 497 | hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => { |
b039eaaf | 498 | // propagate negation, if the negation itself isn't negated |
ba9703b0 XL |
499 | if self.negated_expr_id != Some(e.hir_id) { |
500 | self.negated_expr_id = Some(expr.hir_id); | |
b039eaaf | 501 | } |
c30ab7b3 | 502 | } |
8faf50e0 | 503 | hir::ExprKind::Binary(binop, ref l, ref r) => { |
32a655c1 | 504 | if is_comparison(binop) && !check_limits(cx, binop, &l, &r) { |
74b04a01 XL |
505 | cx.struct_span_lint(UNUSED_COMPARISONS, e.span, |lint| { |
506 | lint.build("comparison is useless due to type limits").emit() | |
507 | }); | |
b039eaaf | 508 | } |
c30ab7b3 | 509 | } |
48663c56 XL |
510 | hir::ExprKind::Lit(ref lit) => lint_literal(cx, self, e, lit), |
511 | _ => {} | |
b039eaaf SL |
512 | }; |
513 | ||
c30ab7b3 | 514 | fn is_valid<T: cmp::PartialOrd>(binop: hir::BinOp, v: T, min: T, max: T) -> bool { |
b039eaaf | 515 | match binop.node { |
8faf50e0 XL |
516 | hir::BinOpKind::Lt => v > min && v <= max, |
517 | hir::BinOpKind::Le => v >= min && v < max, | |
518 | hir::BinOpKind::Gt => v >= min && v < max, | |
519 | hir::BinOpKind::Ge => v > min && v <= max, | |
520 | hir::BinOpKind::Eq | hir::BinOpKind::Ne => v >= min && v <= max, | |
c30ab7b3 | 521 | _ => bug!(), |
b039eaaf SL |
522 | } |
523 | } | |
524 | ||
525 | fn rev_binop(binop: hir::BinOp) -> hir::BinOp { | |
dfeec247 XL |
526 | source_map::respan( |
527 | binop.span, | |
528 | match binop.node { | |
529 | hir::BinOpKind::Lt => hir::BinOpKind::Gt, | |
530 | hir::BinOpKind::Le => hir::BinOpKind::Ge, | |
531 | hir::BinOpKind::Gt => hir::BinOpKind::Lt, | |
532 | hir::BinOpKind::Ge => hir::BinOpKind::Le, | |
533 | _ => return binop, | |
534 | }, | |
535 | ) | |
b039eaaf SL |
536 | } |
537 | ||
dfeec247 | 538 | fn check_limits( |
f035d41b | 539 | cx: &LateContext<'_>, |
dfeec247 XL |
540 | binop: hir::BinOp, |
541 | l: &hir::Expr<'_>, | |
542 | r: &hir::Expr<'_>, | |
543 | ) -> bool { | |
e74abb32 | 544 | let (lit, expr, swap) = match (&l.kind, &r.kind) { |
8faf50e0 XL |
545 | (&hir::ExprKind::Lit(_), _) => (l, r, true), |
546 | (_, &hir::ExprKind::Lit(_)) => (r, l, false), | |
c30ab7b3 | 547 | _ => return true, |
b039eaaf SL |
548 | }; |
549 | // Normalize the binop so that the literal is always on the RHS in | |
550 | // the comparison | |
c30ab7b3 | 551 | let norm_binop = if swap { rev_binop(binop) } else { binop }; |
1b1a35ee | 552 | match *cx.typeck_results().node_type(expr.hir_id).kind() { |
b7449926 | 553 | ty::Int(int_ty) => { |
b039eaaf | 554 | let (min, max) = int_ty_range(int_ty); |
e74abb32 | 555 | let lit_val: i128 = match lit.kind { |
dfeec247 | 556 | hir::ExprKind::Lit(ref li) => match li.node { |
ba9703b0 XL |
557 | ast::LitKind::Int( |
558 | v, | |
559 | ast::LitIntType::Signed(_) | ast::LitIntType::Unsuffixed, | |
560 | ) => v as i128, | |
dfeec247 | 561 | _ => return true, |
32a655c1 | 562 | }, |
dfeec247 | 563 | _ => bug!(), |
b039eaaf SL |
564 | }; |
565 | is_valid(norm_binop, lit_val, min, max) | |
566 | } | |
b7449926 | 567 | ty::Uint(uint_ty) => { |
dfeec247 | 568 | let (min, max): (u128, u128) = uint_ty_range(uint_ty); |
e74abb32 | 569 | let lit_val: u128 = match lit.kind { |
dfeec247 XL |
570 | hir::ExprKind::Lit(ref li) => match li.node { |
571 | ast::LitKind::Int(v, _) => v, | |
572 | _ => return true, | |
32a655c1 | 573 | }, |
dfeec247 | 574 | _ => bug!(), |
b039eaaf SL |
575 | }; |
576 | is_valid(norm_binop, lit_val, min, max) | |
577 | } | |
c30ab7b3 | 578 | _ => true, |
b039eaaf SL |
579 | } |
580 | } | |
581 | ||
582 | fn is_comparison(binop: hir::BinOp) -> bool { | |
29967ef6 XL |
583 | matches!( |
584 | binop.node, | |
dfeec247 | 585 | hir::BinOpKind::Eq |
29967ef6 XL |
586 | | hir::BinOpKind::Lt |
587 | | hir::BinOpKind::Le | |
588 | | hir::BinOpKind::Ne | |
589 | | hir::BinOpKind::Ge | |
590 | | hir::BinOpKind::Gt | |
591 | ) | |
b039eaaf | 592 | } |
b039eaaf SL |
593 | } |
594 | } | |
595 | ||
596 | declare_lint! { | |
1b1a35ee XL |
597 | /// The `improper_ctypes` lint detects incorrect use of types in foreign |
598 | /// modules. | |
599 | /// | |
600 | /// ### Example | |
601 | /// | |
602 | /// ```rust | |
603 | /// extern "C" { | |
604 | /// static STATIC: String; | |
605 | /// } | |
606 | /// ``` | |
607 | /// | |
608 | /// {{produces}} | |
609 | /// | |
610 | /// ### Explanation | |
611 | /// | |
612 | /// The compiler has several checks to verify that types used in `extern` | |
613 | /// blocks are safe and follow certain rules to ensure proper | |
614 | /// compatibility with the foreign interfaces. This lint is issued when it | |
615 | /// detects a probable mistake in a definition. The lint usually should | |
616 | /// provide a description of the issue, along with possibly a hint on how | |
617 | /// to resolve it. | |
b039eaaf SL |
618 | IMPROPER_CTYPES, |
619 | Warn, | |
620 | "proper use of libc types in foreign modules" | |
621 | } | |
622 | ||
f035d41b XL |
623 | declare_lint_pass!(ImproperCTypesDeclarations => [IMPROPER_CTYPES]); |
624 | ||
625 | declare_lint! { | |
1b1a35ee XL |
626 | /// The `improper_ctypes_definitions` lint detects incorrect use of |
627 | /// [`extern` function] definitions. | |
628 | /// | |
629 | /// [`extern` function]: https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier | |
630 | /// | |
631 | /// ### Example | |
632 | /// | |
633 | /// ```rust | |
634 | /// # #![allow(unused)] | |
635 | /// pub extern "C" fn str_type(p: &str) { } | |
636 | /// ``` | |
637 | /// | |
638 | /// {{produces}} | |
639 | /// | |
640 | /// ### Explanation | |
641 | /// | |
642 | /// There are many parameter and return types that may be specified in an | |
643 | /// `extern` function that are not compatible with the given ABI. This | |
644 | /// lint is an alert that these types should not be used. The lint usually | |
645 | /// should provide a description of the issue, along with possibly a hint | |
646 | /// on how to resolve it. | |
f035d41b XL |
647 | IMPROPER_CTYPES_DEFINITIONS, |
648 | Warn, | |
649 | "proper use of libc types in foreign item definitions" | |
650 | } | |
651 | ||
652 | declare_lint_pass!(ImproperCTypesDefinitions => [IMPROPER_CTYPES_DEFINITIONS]); | |
653 | ||
3dfed10e XL |
654 | #[derive(Clone, Copy)] |
655 | crate enum CItemKind { | |
656 | Declaration, | |
657 | Definition, | |
f035d41b | 658 | } |
532ac7d7 | 659 | |
dc9dc135 | 660 | struct ImproperCTypesVisitor<'a, 'tcx> { |
f035d41b | 661 | cx: &'a LateContext<'tcx>, |
3dfed10e | 662 | mode: CItemKind, |
b039eaaf SL |
663 | } |
664 | ||
0531ce1d | 665 | enum FfiResult<'tcx> { |
b039eaaf | 666 | FfiSafe, |
0531ce1d | 667 | FfiPhantom(Ty<'tcx>), |
f035d41b | 668 | FfiUnsafe { ty: Ty<'tcx>, reason: String, help: Option<String> }, |
b039eaaf SL |
669 | } |
670 | ||
1b1a35ee | 671 | crate fn nonnull_optimization_guaranteed<'tcx>(tcx: TyCtxt<'tcx>, def: &ty::AdtDef) -> bool { |
94222f64 | 672 | tcx.get_attrs(def.did).iter().any(|a| a.has_name(sym::rustc_nonnull_optimization_guaranteed)) |
1b1a35ee XL |
673 | } |
674 | ||
675 | /// `repr(transparent)` structs can have a single non-ZST field, this function returns that | |
676 | /// field. | |
677 | pub fn transparent_newtype_field<'a, 'tcx>( | |
678 | tcx: TyCtxt<'tcx>, | |
679 | variant: &'a ty::VariantDef, | |
680 | ) -> Option<&'a ty::FieldDef> { | |
681 | let param_env = tcx.param_env(variant.def_id); | |
17df50a5 | 682 | variant.fields.iter().find(|field| { |
1b1a35ee | 683 | let field_ty = tcx.type_of(field.did); |
5869c6ff | 684 | let is_zst = tcx.layout_of(param_env.and(field_ty)).map_or(false, |layout| layout.is_zst()); |
17df50a5 XL |
685 | !is_zst |
686 | }) | |
1b1a35ee XL |
687 | } |
688 | ||
3dfed10e | 689 | /// Is type known to be non-null? |
6a06907d | 690 | fn ty_is_known_nonnull<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool { |
3dfed10e | 691 | let tcx = cx.tcx; |
1b1a35ee | 692 | match ty.kind() { |
3dfed10e XL |
693 | ty::FnPtr(_) => true, |
694 | ty::Ref(..) => true, | |
695 | ty::Adt(def, _) if def.is_box() && matches!(mode, CItemKind::Definition) => true, | |
696 | ty::Adt(def, substs) if def.repr.transparent() && !def.is_union() => { | |
1b1a35ee | 697 | let marked_non_null = nonnull_optimization_guaranteed(tcx, &def); |
3dfed10e | 698 | |
1b1a35ee | 699 | if marked_non_null { |
3dfed10e | 700 | return true; |
f9652781 | 701 | } |
1b1a35ee | 702 | |
6a06907d XL |
703 | // Types with a `#[repr(no_niche)]` attribute have their niche hidden. |
704 | // The attribute is used by the UnsafeCell for example (the only use so far). | |
705 | if def.repr.hide_niche() { | |
706 | return false; | |
707 | } | |
708 | ||
17df50a5 XL |
709 | def.variants |
710 | .iter() | |
711 | .filter_map(|variant| transparent_newtype_field(cx.tcx, variant)) | |
712 | .any(|field| ty_is_known_nonnull(cx, field.ty(tcx, substs), mode)) | |
b039eaaf | 713 | } |
3dfed10e | 714 | _ => false, |
b039eaaf | 715 | } |
3dfed10e | 716 | } |
dc9dc135 | 717 | |
3dfed10e XL |
718 | /// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type. |
719 | /// If the type passed in was not scalar, returns None. | |
720 | fn get_nullable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { | |
721 | let tcx = cx.tcx; | |
1b1a35ee | 722 | Some(match *ty.kind() { |
3dfed10e XL |
723 | ty::Adt(field_def, field_substs) => { |
724 | let inner_field_ty = { | |
725 | let first_non_zst_ty = | |
1b1a35ee | 726 | field_def.variants.iter().filter_map(|v| transparent_newtype_field(cx.tcx, v)); |
3dfed10e XL |
727 | debug_assert_eq!( |
728 | first_non_zst_ty.clone().count(), | |
729 | 1, | |
730 | "Wrong number of fields for transparent type" | |
731 | ); | |
732 | first_non_zst_ty | |
733 | .last() | |
734 | .expect("No non-zst fields in transparent type.") | |
735 | .ty(tcx, field_substs) | |
736 | }; | |
737 | return get_nullable_type(cx, inner_field_ty); | |
738 | } | |
739 | ty::Int(ty) => tcx.mk_mach_int(ty), | |
740 | ty::Uint(ty) => tcx.mk_mach_uint(ty), | |
741 | ty::RawPtr(ty_mut) => tcx.mk_ptr(ty_mut), | |
742 | // As these types are always non-null, the nullable equivalent of | |
743 | // Option<T> of these types are their raw pointer counterparts. | |
744 | ty::Ref(_region, ty, mutbl) => tcx.mk_ptr(ty::TypeAndMut { ty, mutbl }), | |
745 | ty::FnPtr(..) => { | |
746 | // There is no nullable equivalent for Rust's function pointers -- you | |
747 | // must use an Option<fn(..) -> _> to represent it. | |
748 | ty | |
749 | } | |
750 | ||
751 | // We should only ever reach this case if ty_is_known_nonnull is extended | |
752 | // to other types. | |
753 | ref unhandled => { | |
754 | debug!( | |
755 | "get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}", | |
756 | unhandled, ty | |
757 | ); | |
758 | return None; | |
759 | } | |
760 | }) | |
761 | } | |
762 | ||
763 | /// Check if this enum can be safely exported based on the "nullable pointer optimization". If it | |
1b1a35ee | 764 | /// can, return the type that `ty` can be safely converted to, otherwise return `None`. |
3dfed10e XL |
765 | /// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`, |
766 | /// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes. | |
767 | /// FIXME: This duplicates code in codegen. | |
768 | crate fn repr_nullable_ptr<'tcx>( | |
769 | cx: &LateContext<'tcx>, | |
770 | ty: Ty<'tcx>, | |
771 | ckind: CItemKind, | |
772 | ) -> Option<Ty<'tcx>> { | |
773 | debug!("is_repr_nullable_ptr(cx, ty = {:?})", ty); | |
1b1a35ee | 774 | if let ty::Adt(ty_def, substs) = ty.kind() { |
17df50a5 XL |
775 | let field_ty = match &ty_def.variants.raw[..] { |
776 | [var_one, var_two] => match (&var_one.fields[..], &var_two.fields[..]) { | |
777 | ([], [field]) | ([field], []) => field.ty(cx.tcx, substs), | |
778 | _ => return None, | |
779 | }, | |
780 | _ => return None, | |
f035d41b | 781 | }; |
dc9dc135 | 782 | |
3dfed10e XL |
783 | if !ty_is_known_nonnull(cx, field_ty, ckind) { |
784 | return None; | |
f035d41b | 785 | } |
dc9dc135 | 786 | |
3dfed10e XL |
787 | // At this point, the field's type is known to be nonnull and the parent enum is Option-like. |
788 | // If the computed size for the field and the enum are different, the nonnull optimization isn't | |
789 | // being applied (and we've got a problem somewhere). | |
790 | let compute_size_skeleton = |t| SizeSkeleton::compute(t, cx.tcx, cx.param_env).unwrap(); | |
f035d41b XL |
791 | if !compute_size_skeleton(ty).same_size(compute_size_skeleton(field_ty)) { |
792 | bug!("improper_ctypes: Option nonnull optimization not applied?"); | |
793 | } | |
dc9dc135 | 794 | |
3dfed10e XL |
795 | // Return the nullable type this Option-like enum can be safely represented with. |
796 | let field_ty_abi = &cx.layout_of(field_ty).unwrap().abi; | |
797 | if let Abi::Scalar(field_ty_scalar) = field_ty_abi { | |
94222f64 | 798 | match (field_ty_scalar.valid_range.start, field_ty_scalar.valid_range.end) { |
3dfed10e XL |
799 | (0, _) => unreachable!("Non-null optimisation extended to a non-zero value."), |
800 | (1, _) => { | |
801 | return Some(get_nullable_type(cx, field_ty).unwrap()); | |
802 | } | |
803 | (start, end) => unreachable!("Unhandled start and end range: ({}, {})", start, end), | |
804 | }; | |
805 | } | |
f035d41b | 806 | } |
3dfed10e XL |
807 | None |
808 | } | |
b039eaaf | 809 | |
3dfed10e | 810 | impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { |
60c5eb7d XL |
811 | /// Check if the type is array and emit an unsafe type lint. |
812 | fn check_for_array_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { | |
1b1a35ee | 813 | if let ty::Array(..) = ty.kind() { |
60c5eb7d XL |
814 | self.emit_ffi_unsafe_type_lint( |
815 | ty, | |
816 | sp, | |
817 | "passing raw arrays by value is not FFI-safe", | |
818 | Some("consider passing a pointer to the array"), | |
819 | ); | |
820 | true | |
821 | } else { | |
822 | false | |
823 | } | |
824 | } | |
825 | ||
f035d41b XL |
826 | /// Checks if the given field's type is "ffi-safe". |
827 | fn check_field_type_for_ffi( | |
828 | &self, | |
829 | cache: &mut FxHashSet<Ty<'tcx>>, | |
830 | field: &ty::FieldDef, | |
831 | substs: SubstsRef<'tcx>, | |
832 | ) -> FfiResult<'tcx> { | |
833 | let field_ty = field.ty(self.cx.tcx, substs); | |
834 | if field_ty.has_opaque_types() { | |
835 | self.check_type_for_ffi(cache, field_ty) | |
836 | } else { | |
837 | let field_ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, field_ty); | |
838 | self.check_type_for_ffi(cache, field_ty) | |
839 | } | |
840 | } | |
841 | ||
842 | /// Checks if the given `VariantDef`'s field types are "ffi-safe". | |
843 | fn check_variant_for_ffi( | |
844 | &self, | |
845 | cache: &mut FxHashSet<Ty<'tcx>>, | |
846 | ty: Ty<'tcx>, | |
847 | def: &ty::AdtDef, | |
848 | variant: &ty::VariantDef, | |
849 | substs: SubstsRef<'tcx>, | |
850 | ) -> FfiResult<'tcx> { | |
851 | use FfiResult::*; | |
852 | ||
853 | if def.repr.transparent() { | |
854 | // Can assume that only one field is not a ZST, so only check | |
855 | // that field's type for FFI-safety. | |
1b1a35ee | 856 | if let Some(field) = transparent_newtype_field(self.cx.tcx, variant) { |
f035d41b XL |
857 | self.check_field_type_for_ffi(cache, field, substs) |
858 | } else { | |
859 | bug!("malformed transparent type"); | |
860 | } | |
861 | } else { | |
862 | // We can't completely trust repr(C) markings; make sure the fields are | |
863 | // actually safe. | |
864 | let mut all_phantom = !variant.fields.is_empty(); | |
865 | for field in &variant.fields { | |
866 | match self.check_field_type_for_ffi(cache, &field, substs) { | |
867 | FfiSafe => { | |
868 | all_phantom = false; | |
869 | } | |
870 | FfiPhantom(..) if def.is_enum() => { | |
871 | return FfiUnsafe { | |
872 | ty, | |
873 | reason: "this enum contains a PhantomData field".into(), | |
874 | help: None, | |
875 | }; | |
876 | } | |
877 | FfiPhantom(..) => {} | |
878 | r => return r, | |
879 | } | |
880 | } | |
881 | ||
882 | if all_phantom { FfiPhantom(ty) } else { FfiSafe } | |
883 | } | |
884 | } | |
885 | ||
9fa01778 | 886 | /// Checks if the given type is "ffi-safe" (has a stable, well-defined |
b039eaaf | 887 | /// representation which can be exported to C code). |
dfeec247 | 888 | fn check_type_for_ffi(&self, cache: &mut FxHashSet<Ty<'tcx>>, ty: Ty<'tcx>) -> FfiResult<'tcx> { |
9fa01778 | 889 | use FfiResult::*; |
8bb4bdeb | 890 | |
3dfed10e | 891 | let tcx = self.cx.tcx; |
b039eaaf SL |
892 | |
893 | // Protect against infinite recursion, for example | |
894 | // `struct S(*mut S);`. | |
895 | // FIXME: A recursion limit is necessary as well, for irregular | |
b7449926 | 896 | // recursive types. |
b039eaaf SL |
897 | if !cache.insert(ty) { |
898 | return FfiSafe; | |
899 | } | |
900 | ||
fc512014 | 901 | match *ty.kind() { |
b7449926 | 902 | ty::Adt(def, substs) => { |
17df50a5 XL |
903 | if def.is_box() && matches!(self.mode, CItemKind::Definition) { |
904 | if ty.boxed_ty().is_sized(tcx.at(DUMMY_SP), self.cx.param_env) { | |
905 | return FfiSafe; | |
906 | } else { | |
907 | return FfiUnsafe { | |
908 | ty, | |
94222f64 | 909 | reason: "box cannot be represented as a single pointer".to_string(), |
17df50a5 XL |
910 | help: None, |
911 | }; | |
912 | } | |
913 | } | |
8bb4bdeb | 914 | if def.is_phantom_data() { |
0531ce1d | 915 | return FfiPhantom(ty); |
8bb4bdeb | 916 | } |
c30ab7b3 | 917 | match def.adt_kind() { |
f035d41b XL |
918 | AdtKind::Struct | AdtKind::Union => { |
919 | let kind = if def.is_struct() { "struct" } else { "union" }; | |
920 | ||
2c00a5a8 | 921 | if !def.repr.c() && !def.repr.transparent() { |
0531ce1d | 922 | return FfiUnsafe { |
e1599b0c | 923 | ty, |
f035d41b XL |
924 | reason: format!("this {} has unspecified layout", kind), |
925 | help: Some(format!( | |
dfeec247 | 926 | "consider adding a `#[repr(C)]` or \ |
f035d41b XL |
927 | `#[repr(transparent)]` attribute to this {}", |
928 | kind | |
929 | )), | |
0531ce1d | 930 | }; |
c30ab7b3 | 931 | } |
b039eaaf | 932 | |
e74abb32 XL |
933 | let is_non_exhaustive = |
934 | def.non_enum_variant().is_field_list_non_exhaustive(); | |
935 | if is_non_exhaustive && !def.did.is_local() { | |
936 | return FfiUnsafe { | |
937 | ty, | |
f035d41b | 938 | reason: format!("this {} is non-exhaustive", kind), |
e74abb32 XL |
939 | help: None, |
940 | }; | |
941 | } | |
942 | ||
2c00a5a8 | 943 | if def.non_enum_variant().fields.is_empty() { |
0531ce1d | 944 | return FfiUnsafe { |
e1599b0c | 945 | ty, |
f035d41b XL |
946 | reason: format!("this {} has no fields", kind), |
947 | help: Some(format!("consider adding a member to this {}", kind)), | |
0531ce1d | 948 | }; |
8bb4bdeb XL |
949 | } |
950 | ||
f035d41b | 951 | self.check_variant_for_ffi(cache, ty, def, def.non_enum_variant(), substs) |
b039eaaf | 952 | } |
c30ab7b3 SL |
953 | AdtKind::Enum => { |
954 | if def.variants.is_empty() { | |
955 | // Empty enums are okay... although sort of useless. | |
956 | return FfiSafe; | |
957 | } | |
9e0c209e | 958 | |
c30ab7b3 SL |
959 | // Check for a repr() attribute to specify the size of the |
960 | // discriminant. | |
dc9dc135 | 961 | if !def.repr.c() && !def.repr.transparent() && def.repr.int.is_none() { |
8bb4bdeb | 962 | // Special-case types like `Option<extern fn()>`. |
3dfed10e | 963 | if repr_nullable_ptr(self.cx, ty, self.mode).is_none() { |
0531ce1d | 964 | return FfiUnsafe { |
e1599b0c | 965 | ty, |
f035d41b | 966 | reason: "enum has no representation hint".into(), |
dfeec247 XL |
967 | help: Some( |
968 | "consider adding a `#[repr(C)]`, \ | |
dc9dc135 | 969 | `#[repr(transparent)]`, or integer `#[repr(...)]` \ |
f035d41b XL |
970 | attribute to this enum" |
971 | .into(), | |
dfeec247 | 972 | ), |
0531ce1d | 973 | }; |
9e0c209e | 974 | } |
8bb4bdeb | 975 | } |
c30ab7b3 | 976 | |
e74abb32 XL |
977 | if def.is_variant_list_non_exhaustive() && !def.did.is_local() { |
978 | return FfiUnsafe { | |
979 | ty, | |
f035d41b | 980 | reason: "this enum is non-exhaustive".into(), |
e74abb32 XL |
981 | help: None, |
982 | }; | |
983 | } | |
984 | ||
c30ab7b3 SL |
985 | // Check the contained variants. |
986 | for variant in &def.variants { | |
e74abb32 XL |
987 | let is_non_exhaustive = variant.is_field_list_non_exhaustive(); |
988 | if is_non_exhaustive && !variant.def_id.is_local() { | |
989 | return FfiUnsafe { | |
990 | ty, | |
f035d41b | 991 | reason: "this enum has non-exhaustive variants".into(), |
e74abb32 XL |
992 | help: None, |
993 | }; | |
994 | } | |
995 | ||
f035d41b XL |
996 | match self.check_variant_for_ffi(cache, ty, def, variant, substs) { |
997 | FfiSafe => (), | |
998 | r => return r, | |
9e0c209e | 999 | } |
b039eaaf | 1000 | } |
f035d41b | 1001 | |
c30ab7b3 | 1002 | FfiSafe |
b039eaaf SL |
1003 | } |
1004 | } | |
c30ab7b3 | 1005 | } |
b039eaaf | 1006 | |
b7449926 | 1007 | ty::Char => FfiUnsafe { |
e1599b0c | 1008 | ty, |
f035d41b XL |
1009 | reason: "the `char` type has no C equivalent".into(), |
1010 | help: Some("consider using `u32` or `libc::wchar_t` instead".into()), | |
0531ce1d | 1011 | }, |
b039eaaf | 1012 | |
5869c6ff | 1013 | ty::Int(ty::IntTy::I128) | ty::Uint(ty::UintTy::U128) => FfiUnsafe { |
e1599b0c | 1014 | ty, |
f035d41b | 1015 | reason: "128-bit integers don't currently have a known stable ABI".into(), |
0531ce1d XL |
1016 | help: None, |
1017 | }, | |
ea8adc8c | 1018 | |
b039eaaf | 1019 | // Primitive types with a stable representation. |
b7449926 | 1020 | ty::Bool | ty::Int(..) | ty::Uint(..) | ty::Float(..) | ty::Never => FfiSafe, |
b039eaaf | 1021 | |
b7449926 | 1022 | ty::Slice(_) => FfiUnsafe { |
e1599b0c | 1023 | ty, |
f035d41b XL |
1024 | reason: "slices have no C equivalent".into(), |
1025 | help: Some("consider using a raw pointer instead".into()), | |
0531ce1d XL |
1026 | }, |
1027 | ||
dfeec247 | 1028 | ty::Dynamic(..) => { |
f035d41b | 1029 | FfiUnsafe { ty, reason: "trait objects have no C equivalent".into(), help: None } |
dfeec247 | 1030 | } |
0531ce1d | 1031 | |
b7449926 | 1032 | ty::Str => FfiUnsafe { |
e1599b0c | 1033 | ty, |
f035d41b XL |
1034 | reason: "string slices have no C equivalent".into(), |
1035 | help: Some("consider using `*const u8` and a length instead".into()), | |
0531ce1d XL |
1036 | }, |
1037 | ||
b7449926 | 1038 | ty::Tuple(..) => FfiUnsafe { |
e1599b0c | 1039 | ty, |
f035d41b XL |
1040 | reason: "tuples have unspecified layout".into(), |
1041 | help: Some("consider using a struct instead".into()), | |
0531ce1d | 1042 | }, |
b039eaaf | 1043 | |
f035d41b XL |
1044 | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) |
1045 | if { | |
3dfed10e | 1046 | matches!(self.mode, CItemKind::Definition) |
f035d41b XL |
1047 | && ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env) |
1048 | } => | |
1049 | { | |
1050 | FfiSafe | |
1051 | } | |
1052 | ||
dfeec247 XL |
1053 | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => { |
1054 | self.check_type_for_ffi(cache, ty) | |
1055 | } | |
b039eaaf | 1056 | |
60c5eb7d | 1057 | ty::Array(inner_ty, _) => self.check_type_for_ffi(cache, inner_ty), |
b039eaaf | 1058 | |
b7449926 | 1059 | ty::FnPtr(sig) => { |
f035d41b XL |
1060 | if self.is_internal_abi(sig.abi()) { |
1061 | return FfiUnsafe { | |
1062 | ty, | |
1063 | reason: "this function pointer has Rust-specific calling convention".into(), | |
1064 | help: Some( | |
1065 | "consider using an `extern fn(...) -> ...` \ | |
1066 | function pointer instead" | |
1067 | .into(), | |
1068 | ), | |
1069 | }; | |
b039eaaf SL |
1070 | } |
1071 | ||
fc512014 | 1072 | let sig = tcx.erase_late_bound_regions(sig); |
b7449926 | 1073 | if !sig.output().is_unit() { |
476ff2be | 1074 | let r = self.check_type_for_ffi(cache, sig.output()); |
5bcae85e SL |
1075 | match r { |
1076 | FfiSafe => {} | |
c30ab7b3 SL |
1077 | _ => { |
1078 | return r; | |
1079 | } | |
b039eaaf SL |
1080 | } |
1081 | } | |
476ff2be | 1082 | for arg in sig.inputs() { |
b039eaaf SL |
1083 | let r = self.check_type_for_ffi(cache, arg); |
1084 | match r { | |
1085 | FfiSafe => {} | |
c30ab7b3 SL |
1086 | _ => { |
1087 | return r; | |
1088 | } | |
b039eaaf SL |
1089 | } |
1090 | } | |
1091 | FfiSafe | |
1092 | } | |
1093 | ||
b7449926 XL |
1094 | ty::Foreign(..) => FfiSafe, |
1095 | ||
f035d41b XL |
1096 | // While opaque types are checked for earlier, if a projection in a struct field |
1097 | // normalizes to an opaque type, then it will reach this branch. | |
1098 | ty::Opaque(..) => { | |
1099 | FfiUnsafe { ty, reason: "opaque types have no C equivalent".into(), help: None } | |
1100 | } | |
1101 | ||
1102 | // `extern "C" fn` functions can have type parameters, which may or may not be FFI-safe, | |
1103 | // so they are currently ignored for the purposes of this lint. | |
3dfed10e | 1104 | ty::Param(..) | ty::Projection(..) if matches!(self.mode, CItemKind::Definition) => { |
f035d41b XL |
1105 | FfiSafe |
1106 | } | |
1107 | ||
dfeec247 | 1108 | ty::Param(..) |
f035d41b | 1109 | | ty::Projection(..) |
dfeec247 XL |
1110 | | ty::Infer(..) |
1111 | | ty::Bound(..) | |
f035d41b | 1112 | | ty::Error(_) |
dfeec247 XL |
1113 | | ty::Closure(..) |
1114 | | ty::Generator(..) | |
1115 | | ty::GeneratorWitness(..) | |
1116 | | ty::Placeholder(..) | |
dfeec247 | 1117 | | ty::FnDef(..) => bug!("unexpected type in foreign function: {:?}", ty), |
e1599b0c XL |
1118 | } |
1119 | } | |
1120 | ||
1121 | fn emit_ffi_unsafe_type_lint( | |
1122 | &mut self, | |
1123 | ty: Ty<'tcx>, | |
1124 | sp: Span, | |
1125 | note: &str, | |
1126 | help: Option<&str>, | |
1127 | ) { | |
f035d41b | 1128 | let lint = match self.mode { |
3dfed10e XL |
1129 | CItemKind::Declaration => IMPROPER_CTYPES, |
1130 | CItemKind::Definition => IMPROPER_CTYPES_DEFINITIONS, | |
f035d41b XL |
1131 | }; |
1132 | ||
1133 | self.cx.struct_span_lint(lint, sp, |lint| { | |
1134 | let item_description = match self.mode { | |
3dfed10e XL |
1135 | CItemKind::Declaration => "block", |
1136 | CItemKind::Definition => "fn", | |
f035d41b XL |
1137 | }; |
1138 | let mut diag = lint.build(&format!( | |
1139 | "`extern` {} uses type `{}`, which is not FFI-safe", | |
1140 | item_description, ty | |
1141 | )); | |
74b04a01 XL |
1142 | diag.span_label(sp, "not FFI-safe"); |
1143 | if let Some(help) = help { | |
1144 | diag.help(help); | |
e1599b0c | 1145 | } |
74b04a01 | 1146 | diag.note(note); |
1b1a35ee | 1147 | if let ty::Adt(def, _) = ty.kind() { |
74b04a01 XL |
1148 | if let Some(sp) = self.cx.tcx.hir().span_if_local(def.did) { |
1149 | diag.span_note(sp, "the type is defined here"); | |
1150 | } | |
1151 | } | |
1152 | diag.emit(); | |
1153 | }); | |
e1599b0c XL |
1154 | } |
1155 | ||
1156 | fn check_for_opaque_ty(&mut self, sp: Span, ty: Ty<'tcx>) -> bool { | |
f035d41b XL |
1157 | struct ProhibitOpaqueTypes<'a, 'tcx> { |
1158 | cx: &'a LateContext<'tcx>, | |
fc512014 | 1159 | } |
e1599b0c | 1160 | |
f035d41b | 1161 | impl<'a, 'tcx> ty::fold::TypeVisitor<'tcx> for ProhibitOpaqueTypes<'a, 'tcx> { |
fc512014 | 1162 | type BreakTy = Ty<'tcx>; |
94222f64 XL |
1163 | fn tcx_for_anon_const_substs(&self) -> Option<TyCtxt<'tcx>> { |
1164 | Some(self.cx.tcx) | |
1165 | } | |
fc512014 XL |
1166 | |
1167 | fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { | |
1b1a35ee | 1168 | match ty.kind() { |
fc512014 | 1169 | ty::Opaque(..) => ControlFlow::Break(ty), |
f035d41b XL |
1170 | // Consider opaque types within projections FFI-safe if they do not normalize |
1171 | // to more opaque types. | |
1172 | ty::Projection(..) => { | |
1173 | let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); | |
1174 | ||
94222f64 | 1175 | // If `ty` is an opaque type directly then `super_visit_with` won't invoke |
f035d41b | 1176 | // this function again. |
29967ef6 XL |
1177 | if ty.has_opaque_types() { |
1178 | self.visit_ty(ty) | |
1179 | } else { | |
1180 | ControlFlow::CONTINUE | |
1181 | } | |
f035d41b XL |
1182 | } |
1183 | _ => ty.super_visit_with(self), | |
e1599b0c XL |
1184 | } |
1185 | } | |
1186 | } | |
1187 | ||
fc512014 | 1188 | if let Some(ty) = ty.visit_with(&mut ProhibitOpaqueTypes { cx: self.cx }).break_value() { |
dfeec247 | 1189 | self.emit_ffi_unsafe_type_lint(ty, sp, "opaque types have no C equivalent", None); |
e1599b0c XL |
1190 | true |
1191 | } else { | |
1192 | false | |
b039eaaf SL |
1193 | } |
1194 | } | |
1195 | ||
f035d41b XL |
1196 | fn check_type_for_ffi_and_report_errors( |
1197 | &mut self, | |
1198 | sp: Span, | |
1199 | ty: Ty<'tcx>, | |
1200 | is_static: bool, | |
1201 | is_return_type: bool, | |
1202 | ) { | |
e1599b0c XL |
1203 | // We have to check for opaque types before `normalize_erasing_regions`, |
1204 | // which will replace opaque types with their underlying concrete type. | |
1205 | if self.check_for_opaque_ty(sp, ty) { | |
1206 | // We've already emitted an error due to an opaque type. | |
1207 | return; | |
1208 | } | |
1209 | ||
54a0048b SL |
1210 | // it is only OK to use this function because extern fns cannot have |
1211 | // any generic types right now: | |
f035d41b XL |
1212 | let ty = self.cx.tcx.normalize_erasing_regions(self.cx.param_env, ty); |
1213 | ||
1214 | // C doesn't really support passing arrays by value - the only way to pass an array by value | |
1215 | // is through a struct. So, first test that the top level isn't an array, and then | |
1216 | // recursively check the types inside. | |
60c5eb7d XL |
1217 | if !is_static && self.check_for_array_ty(sp, ty) { |
1218 | return; | |
1219 | } | |
b039eaaf | 1220 | |
f035d41b XL |
1221 | // Don't report FFI errors for unit return types. This check exists here, and not in |
1222 | // `check_foreign_fn` (where it would make more sense) so that normalization has definitely | |
1223 | // happened. | |
1224 | if is_return_type && ty.is_unit() { | |
1225 | return; | |
1226 | } | |
1227 | ||
0bf4aa26 | 1228 | match self.check_type_for_ffi(&mut FxHashSet::default(), ty) { |
b039eaaf | 1229 | FfiResult::FfiSafe => {} |
0531ce1d | 1230 | FfiResult::FfiPhantom(ty) => { |
e1599b0c | 1231 | self.emit_ffi_unsafe_type_lint(ty, sp, "composed only of `PhantomData`", None); |
9e0c209e | 1232 | } |
f035d41b XL |
1233 | // If `ty` is a `repr(transparent)` newtype, and the non-zero-sized type is a generic |
1234 | // argument, which after substitution, is `()`, then this branch can be hit. | |
3dfed10e | 1235 | FfiResult::FfiUnsafe { ty, .. } if is_return_type && ty.is_unit() => {} |
e1599b0c | 1236 | FfiResult::FfiUnsafe { ty, reason, help } => { |
f035d41b | 1237 | self.emit_ffi_unsafe_type_lint(ty, sp, &reason, help.as_deref()); |
b039eaaf SL |
1238 | } |
1239 | } | |
1240 | } | |
b039eaaf | 1241 | |
dfeec247 | 1242 | fn check_foreign_fn(&mut self, id: hir::HirId, decl: &hir::FnDecl<'_>) { |
416331ca | 1243 | let def_id = self.cx.tcx.hir().local_def_id(id); |
041b39d2 | 1244 | let sig = self.cx.tcx.fn_sig(def_id); |
fc512014 | 1245 | let sig = self.cx.tcx.erase_late_bound_regions(sig); |
54a0048b | 1246 | |
cdc7bbd5 | 1247 | for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) { |
f035d41b | 1248 | self.check_type_for_ffi_and_report_errors(input_hir.span, input_ty, false, false); |
54a0048b SL |
1249 | } |
1250 | ||
74b04a01 | 1251 | if let hir::FnRetTy::Return(ref ret_hir) = decl.output { |
476ff2be | 1252 | let ret_ty = sig.output(); |
f035d41b | 1253 | self.check_type_for_ffi_and_report_errors(ret_hir.span, ret_ty, false, true); |
b039eaaf SL |
1254 | } |
1255 | } | |
54a0048b | 1256 | |
532ac7d7 | 1257 | fn check_foreign_static(&mut self, id: hir::HirId, span: Span) { |
416331ca | 1258 | let def_id = self.cx.tcx.hir().local_def_id(id); |
7cac9316 | 1259 | let ty = self.cx.tcx.type_of(def_id); |
f035d41b | 1260 | self.check_type_for_ffi_and_report_errors(span, ty, true, false); |
54a0048b | 1261 | } |
b039eaaf | 1262 | |
3dfed10e | 1263 | fn is_internal_abi(&self, abi: SpecAbi) -> bool { |
29967ef6 XL |
1264 | matches!( |
1265 | abi, | |
1266 | SpecAbi::Rust | SpecAbi::RustCall | SpecAbi::RustIntrinsic | SpecAbi::PlatformIntrinsic | |
1267 | ) | |
f035d41b XL |
1268 | } |
1269 | } | |
1270 | ||
1271 | impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDeclarations { | |
1272 | fn check_foreign_item(&mut self, cx: &LateContext<'_>, it: &hir::ForeignItem<'_>) { | |
3dfed10e | 1273 | let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Declaration }; |
6a06907d | 1274 | let abi = cx.tcx.hir().get_foreign_abi(it.hir_id()); |
f035d41b XL |
1275 | |
1276 | if !vis.is_internal_abi(abi) { | |
e74abb32 | 1277 | match it.kind { |
b7449926 | 1278 | hir::ForeignItemKind::Fn(ref decl, _, _) => { |
6a06907d | 1279 | vis.check_foreign_fn(it.hir_id(), decl); |
b7449926 XL |
1280 | } |
1281 | hir::ForeignItemKind::Static(ref ty, _) => { | |
6a06907d | 1282 | vis.check_foreign_static(it.hir_id(), ty.span); |
b039eaaf | 1283 | } |
dfeec247 | 1284 | hir::ForeignItemKind::Type => (), |
b039eaaf | 1285 | } |
b039eaaf SL |
1286 | } |
1287 | } | |
1288 | } | |
5bcae85e | 1289 | |
f035d41b XL |
1290 | impl<'tcx> LateLintPass<'tcx> for ImproperCTypesDefinitions { |
1291 | fn check_fn( | |
1292 | &mut self, | |
1293 | cx: &LateContext<'tcx>, | |
1294 | kind: hir::intravisit::FnKind<'tcx>, | |
1295 | decl: &'tcx hir::FnDecl<'_>, | |
1296 | _: &'tcx hir::Body<'_>, | |
1297 | _: Span, | |
1298 | hir_id: hir::HirId, | |
1299 | ) { | |
1300 | use hir::intravisit::FnKind; | |
1301 | ||
1302 | let abi = match kind { | |
1303 | FnKind::ItemFn(_, _, header, ..) => header.abi, | |
1304 | FnKind::Method(_, sig, ..) => sig.header.abi, | |
1305 | _ => return, | |
1306 | }; | |
1307 | ||
3dfed10e | 1308 | let mut vis = ImproperCTypesVisitor { cx, mode: CItemKind::Definition }; |
f035d41b XL |
1309 | if !vis.is_internal_abi(abi) { |
1310 | vis.check_foreign_fn(hir_id, decl); | |
1311 | } | |
1312 | } | |
1313 | } | |
1314 | ||
532ac7d7 | 1315 | declare_lint_pass!(VariantSizeDifferences => [VARIANT_SIZE_DIFFERENCES]); |
5bcae85e | 1316 | |
f035d41b XL |
1317 | impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences { |
1318 | fn check_item(&mut self, cx: &LateContext<'_>, it: &hir::Item<'_>) { | |
e74abb32 | 1319 | if let hir::ItemKind::Enum(ref enum_definition, _) = it.kind { |
6a06907d | 1320 | let t = cx.tcx.type_of(it.def_id); |
fc512014 | 1321 | let ty = cx.tcx.erase_regions(t); |
532ac7d7 XL |
1322 | let layout = match cx.layout_of(ty) { |
1323 | Ok(layout) => layout, | |
ba9703b0 XL |
1324 | Err( |
1325 | ty::layout::LayoutError::Unknown(_) | ty::layout::LayoutError::SizeOverflow(_), | |
1326 | ) => return, | |
532ac7d7 XL |
1327 | }; |
1328 | let (variants, tag) = match layout.variants { | |
ba9703b0 | 1329 | Variants::Multiple { |
f035d41b XL |
1330 | tag_encoding: TagEncoding::Direct, |
1331 | ref tag, | |
532ac7d7 | 1332 | ref variants, |
48663c56 | 1333 | .. |
f035d41b | 1334 | } => (variants, tag), |
532ac7d7 XL |
1335 | _ => return, |
1336 | }; | |
1337 | ||
f035d41b | 1338 | let tag_size = tag.value.size(&cx.tcx).bytes(); |
532ac7d7 | 1339 | |
dfeec247 XL |
1340 | debug!( |
1341 | "enum `{}` is {} bytes large with layout:\n{:#?}", | |
1342 | t, | |
1343 | layout.size.bytes(), | |
1344 | layout | |
1345 | ); | |
532ac7d7 | 1346 | |
cdc7bbd5 | 1347 | let (largest, slargest, largest_index) = iter::zip(enum_definition.variants, variants) |
532ac7d7 | 1348 | .map(|(variant, variant_layout)| { |
f035d41b XL |
1349 | // Subtract the size of the enum tag. |
1350 | let bytes = variant_layout.size.bytes().saturating_sub(tag_size); | |
532ac7d7 | 1351 | |
dfeec247 | 1352 | debug!("- variant `{}` is {} bytes large", variant.ident, bytes); |
532ac7d7 XL |
1353 | bytes |
1354 | }) | |
1355 | .enumerate() | |
dfeec247 XL |
1356 | .fold((0, 0, 0), |(l, s, li), (idx, size)| { |
1357 | if size > l { | |
1358 | (size, l, idx) | |
1359 | } else if size > s { | |
1360 | (l, size, li) | |
1361 | } else { | |
1362 | (l, s, li) | |
1363 | } | |
532ac7d7 XL |
1364 | }); |
1365 | ||
1366 | // We only warn if the largest variant is at least thrice as large as | |
1367 | // the second-largest. | |
1368 | if largest > slargest * 3 && slargest > 0 { | |
74b04a01 | 1369 | cx.struct_span_lint( |
dfeec247 XL |
1370 | VARIANT_SIZE_DIFFERENCES, |
1371 | enum_definition.variants[largest_index].span, | |
74b04a01 XL |
1372 | |lint| { |
1373 | lint.build(&format!( | |
1374 | "enum variant is more than three times \ | |
532ac7d7 | 1375 | larger ({} bytes) than the next largest", |
74b04a01 XL |
1376 | largest |
1377 | )) | |
1378 | .emit() | |
1379 | }, | |
dfeec247 | 1380 | ); |
5bcae85e SL |
1381 | } |
1382 | } | |
1383 | } | |
1384 | } | |
94222f64 XL |
1385 | |
1386 | declare_lint! { | |
1387 | /// The `invalid_atomic_ordering` lint detects passing an `Ordering` | |
1388 | /// to an atomic operation that does not support that ordering. | |
1389 | /// | |
1390 | /// ### Example | |
1391 | /// | |
1392 | /// ```rust,compile_fail | |
1393 | /// # use core::sync::atomic::{AtomicU8, Ordering}; | |
1394 | /// let atom = AtomicU8::new(0); | |
1395 | /// let value = atom.load(Ordering::Release); | |
1396 | /// # let _ = value; | |
1397 | /// ``` | |
1398 | /// | |
1399 | /// {{produces}} | |
1400 | /// | |
1401 | /// ### Explanation | |
1402 | /// | |
1403 | /// Some atomic operations are only supported for a subset of the | |
1404 | /// `atomic::Ordering` variants. Passing an unsupported variant will cause | |
1405 | /// an unconditional panic at runtime, which is detected by this lint. | |
1406 | /// | |
1407 | /// This lint will trigger in the following cases: (where `AtomicType` is an | |
1408 | /// atomic type from `core::sync::atomic`, such as `AtomicBool`, | |
1409 | /// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics). | |
1410 | /// | |
1411 | /// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to | |
1412 | /// `AtomicType::store`. | |
1413 | /// | |
1414 | /// - Passing `Ordering::Release` or `Ordering::AcqRel` to | |
1415 | /// `AtomicType::load`. | |
1416 | /// | |
1417 | /// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or | |
1418 | /// `core::sync::atomic::compiler_fence`. | |
1419 | /// | |
1420 | /// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure | |
1421 | /// ordering for any of `AtomicType::compare_exchange`, | |
1422 | /// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`. | |
1423 | /// | |
1424 | /// - Passing in a pair of orderings to `AtomicType::compare_exchange`, | |
1425 | /// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update` | |
1426 | /// where the failure ordering is stronger than the success ordering. | |
1427 | INVALID_ATOMIC_ORDERING, | |
1428 | Deny, | |
1429 | "usage of invalid atomic ordering in atomic operations and memory fences" | |
1430 | } | |
1431 | ||
1432 | declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]); | |
1433 | ||
1434 | impl InvalidAtomicOrdering { | |
1435 | fn inherent_atomic_method_call<'hir>( | |
1436 | cx: &LateContext<'_>, | |
1437 | expr: &Expr<'hir>, | |
1438 | recognized_names: &[Symbol], // used for fast path calculation | |
1439 | ) -> Option<(Symbol, &'hir [Expr<'hir>])> { | |
1440 | const ATOMIC_TYPES: &[Symbol] = &[ | |
1441 | sym::AtomicBool, | |
1442 | sym::AtomicPtr, | |
1443 | sym::AtomicUsize, | |
1444 | sym::AtomicU8, | |
1445 | sym::AtomicU16, | |
1446 | sym::AtomicU32, | |
1447 | sym::AtomicU64, | |
1448 | sym::AtomicU128, | |
1449 | sym::AtomicIsize, | |
1450 | sym::AtomicI8, | |
1451 | sym::AtomicI16, | |
1452 | sym::AtomicI32, | |
1453 | sym::AtomicI64, | |
1454 | sym::AtomicI128, | |
1455 | ]; | |
1456 | if_chain! { | |
1457 | if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind; | |
1458 | if recognized_names.contains(&method_path.ident.name); | |
1459 | if let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); | |
1460 | if let Some(impl_did) = cx.tcx.impl_of_method(m_def_id); | |
1461 | if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def(); | |
1462 | // skip extension traits, only lint functions from the standard library | |
1463 | if cx.tcx.trait_id_of_impl(impl_did).is_none(); | |
1464 | ||
1465 | if let Some(parent) = cx.tcx.parent(adt.did); | |
1466 | if cx.tcx.is_diagnostic_item(sym::atomic_mod, parent); | |
1467 | if ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did)); | |
1468 | then { | |
1469 | return Some((method_path.ident.name, args)); | |
1470 | } | |
1471 | } | |
1472 | None | |
1473 | } | |
1474 | ||
1475 | fn matches_ordering(cx: &LateContext<'_>, did: DefId, orderings: &[Symbol]) -> bool { | |
1476 | let tcx = cx.tcx; | |
1477 | let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering); | |
1478 | orderings.iter().any(|ordering| { | |
1479 | tcx.item_name(did) == *ordering && { | |
1480 | let parent = tcx.parent(did); | |
1481 | parent == atomic_ordering | |
1482 | // needed in case this is a ctor, not a variant | |
1483 | || parent.map_or(false, |parent| tcx.parent(parent) == atomic_ordering) | |
1484 | } | |
1485 | }) | |
1486 | } | |
1487 | ||
1488 | fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId> { | |
1489 | if let ExprKind::Path(ref ord_qpath) = ord_arg.kind { | |
1490 | cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id() | |
1491 | } else { | |
1492 | None | |
1493 | } | |
1494 | } | |
1495 | ||
1496 | fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
1497 | use rustc_hir::def::{DefKind, Res}; | |
1498 | use rustc_hir::QPath; | |
1499 | if_chain! { | |
1500 | if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store]); | |
1501 | if let Some((ordering_arg, invalid_ordering)) = match method { | |
1502 | sym::load => Some((&args[1], sym::Release)), | |
1503 | sym::store => Some((&args[2], sym::Acquire)), | |
1504 | _ => None, | |
1505 | }; | |
1506 | ||
1507 | if let ExprKind::Path(QPath::Resolved(_, path)) = ordering_arg.kind; | |
1508 | if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res; | |
1509 | if Self::matches_ordering(cx, ctor_id, &[invalid_ordering, sym::AcqRel]); | |
1510 | then { | |
1511 | cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| { | |
1512 | if method == sym::load { | |
1513 | diag.build("atomic loads cannot have `Release` or `AcqRel` ordering") | |
1514 | .help("consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`") | |
1515 | .emit() | |
1516 | } else { | |
1517 | debug_assert_eq!(method, sym::store); | |
1518 | diag.build("atomic stores cannot have `Acquire` or `AcqRel` ordering") | |
1519 | .help("consider using ordering modes `Release`, `SeqCst` or `Relaxed`") | |
1520 | .emit(); | |
1521 | } | |
1522 | }); | |
1523 | } | |
1524 | } | |
1525 | } | |
1526 | ||
1527 | fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
1528 | if_chain! { | |
1529 | if let ExprKind::Call(ref func, ref args) = expr.kind; | |
1530 | if let ExprKind::Path(ref func_qpath) = func.kind; | |
1531 | if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); | |
1532 | if cx.tcx.is_diagnostic_item(sym::fence, def_id) || | |
1533 | cx.tcx.is_diagnostic_item(sym::compiler_fence, def_id); | |
1534 | if let ExprKind::Path(ref ordering_qpath) = &args[0].kind; | |
1535 | if let Some(ordering_def_id) = cx.qpath_res(ordering_qpath, args[0].hir_id).opt_def_id(); | |
1536 | if Self::matches_ordering(cx, ordering_def_id, &[sym::Relaxed]); | |
1537 | then { | |
1538 | cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| { | |
1539 | diag.build("memory fences cannot have `Relaxed` ordering") | |
1540 | .help("consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`") | |
1541 | .emit(); | |
1542 | }); | |
1543 | } | |
1544 | } | |
1545 | } | |
1546 | ||
1547 | fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) { | |
1548 | if_chain! { | |
1549 | if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak]); | |
1550 | if let Some((success_order_arg, failure_order_arg)) = match method { | |
1551 | sym::fetch_update => Some((&args[1], &args[2])), | |
1552 | sym::compare_exchange | sym::compare_exchange_weak => Some((&args[3], &args[4])), | |
1553 | _ => None, | |
1554 | }; | |
1555 | ||
1556 | if let Some(fail_ordering_def_id) = Self::opt_ordering_defid(cx, failure_order_arg); | |
1557 | then { | |
1558 | // Helper type holding on to some checking and error reporting data. Has | |
1559 | // - (success ordering, | |
1560 | // - list of failure orderings forbidden by the success order, | |
1561 | // - suggestion message) | |
1562 | type OrdLintInfo = (Symbol, &'static [Symbol], &'static str); | |
1563 | const RELAXED: OrdLintInfo = (sym::Relaxed, &[sym::SeqCst, sym::Acquire], "ordering mode `Relaxed`"); | |
1564 | const ACQUIRE: OrdLintInfo = (sym::Acquire, &[sym::SeqCst], "ordering modes `Acquire` or `Relaxed`"); | |
1565 | const SEQ_CST: OrdLintInfo = (sym::SeqCst, &[], "ordering modes `Acquire`, `SeqCst` or `Relaxed`"); | |
1566 | const RELEASE: OrdLintInfo = (sym::Release, RELAXED.1, RELAXED.2); | |
1567 | const ACQREL: OrdLintInfo = (sym::AcqRel, ACQUIRE.1, ACQUIRE.2); | |
1568 | const SEARCH: [OrdLintInfo; 5] = [RELAXED, ACQUIRE, SEQ_CST, RELEASE, ACQREL]; | |
1569 | ||
1570 | let success_lint_info = Self::opt_ordering_defid(cx, success_order_arg) | |
1571 | .and_then(|success_ord_def_id| -> Option<OrdLintInfo> { | |
1572 | SEARCH | |
1573 | .iter() | |
1574 | .copied() | |
1575 | .find(|(ordering, ..)| { | |
1576 | Self::matches_ordering(cx, success_ord_def_id, &[*ordering]) | |
1577 | }) | |
1578 | }); | |
1579 | if Self::matches_ordering(cx, fail_ordering_def_id, &[sym::Release, sym::AcqRel]) { | |
1580 | // If we don't know the success order is, use what we'd suggest | |
1581 | // if it were maximally permissive. | |
1582 | let suggested = success_lint_info.unwrap_or(SEQ_CST).2; | |
1583 | cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| { | |
1584 | let msg = format!( | |
1585 | "{}'s failure ordering may not be `Release` or `AcqRel`", | |
1586 | method, | |
1587 | ); | |
1588 | diag.build(&msg) | |
1589 | .help(&format!("consider using {} instead", suggested)) | |
1590 | .emit(); | |
1591 | }); | |
1592 | } else if let Some((success_ord, bad_ords_given_success, suggested)) = success_lint_info { | |
1593 | if Self::matches_ordering(cx, fail_ordering_def_id, bad_ords_given_success) { | |
1594 | cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| { | |
1595 | let msg = format!( | |
1596 | "{}'s failure ordering may not be stronger than the success ordering of `{}`", | |
1597 | method, | |
1598 | success_ord, | |
1599 | ); | |
1600 | diag.build(&msg) | |
1601 | .help(&format!("consider using {} instead", suggested)) | |
1602 | .emit(); | |
1603 | }); | |
1604 | } | |
1605 | } | |
1606 | } | |
1607 | } | |
1608 | } | |
1609 | } | |
1610 | ||
1611 | impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering { | |
1612 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { | |
1613 | Self::check_atomic_load_store(cx, expr); | |
1614 | Self::check_memory_fence(cx, expr); | |
1615 | Self::check_atomic_compare_exchange(cx, expr); | |
1616 | } | |
1617 | } |