]>
Commit | Line | Data |
---|---|---|
add651ee FG |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable}; | |
9ffffee4 | 3 | use core::ops::ControlFlow::Continue; |
add651ee FG |
4 | use hir::def::{DefKind, Res}; |
5 | use hir::{BlockCheckMode, ExprKind, QPath, UnOp, Unsafety}; | |
9ffffee4 FG |
6 | use rustc_ast::Mutability; |
7 | use rustc_hir as hir; | |
8 | use rustc_lint::{LateContext, LateLintPass}; | |
9 | use rustc_middle::lint::in_external_macro; | |
353b0b11 | 10 | use rustc_middle::ty; |
9ffffee4 FG |
11 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
12 | use rustc_span::Span; | |
13 | ||
14 | declare_clippy_lint! { | |
15 | /// ### What it does | |
16 | /// Checks for `unsafe` blocks that contain more than one unsafe operation. | |
17 | /// | |
18 | /// ### Why is this bad? | |
19 | /// Combined with `undocumented_unsafe_blocks`, | |
20 | /// this lint ensures that each unsafe operation must be independently justified. | |
21 | /// Combined with `unused_unsafe`, this lint also ensures | |
22 | /// elimination of unnecessary unsafe blocks through refactoring. | |
23 | /// | |
24 | /// ### Example | |
25 | /// ```rust | |
26 | /// /// Reads a `char` from the given pointer. | |
27 | /// /// | |
28 | /// /// # Safety | |
29 | /// /// | |
30 | /// /// `ptr` must point to four consecutive, initialized bytes which | |
31 | /// /// form a valid `char` when interpreted in the native byte order. | |
32 | /// fn read_char(ptr: *const u8) -> char { | |
33 | /// // SAFETY: The caller has guaranteed that the value pointed | |
34 | /// // to by `bytes` is a valid `char`. | |
35 | /// unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } | |
36 | /// } | |
37 | /// ``` | |
38 | /// Use instead: | |
39 | /// ```rust | |
40 | /// /// Reads a `char` from the given pointer. | |
41 | /// /// | |
42 | /// /// # Safety | |
43 | /// /// | |
44 | /// /// - `ptr` must be 4-byte aligned, point to four consecutive | |
45 | /// /// initialized bytes, and be valid for reads of 4 bytes. | |
46 | /// /// - The bytes pointed to by `ptr` must represent a valid | |
47 | /// /// `char` when interpreted in the native byte order. | |
48 | /// fn read_char(ptr: *const u8) -> char { | |
49 | /// // SAFETY: `ptr` is 4-byte aligned, points to four consecutive | |
50 | /// // initialized bytes, and is valid for reads of 4 bytes. | |
51 | /// let int_value = unsafe { *ptr.cast::<u32>() }; | |
52 | /// | |
53 | /// // SAFETY: The caller has guaranteed that the four bytes | |
54 | /// // pointed to by `bytes` represent a valid `char`. | |
55 | /// unsafe { char::from_u32_unchecked(int_value) } | |
56 | /// } | |
57 | /// ``` | |
49aad941 | 58 | #[clippy::version = "1.69.0"] |
9ffffee4 FG |
59 | pub MULTIPLE_UNSAFE_OPS_PER_BLOCK, |
60 | restriction, | |
61 | "more than one unsafe operation per `unsafe` block" | |
62 | } | |
63 | declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]); | |
64 | ||
65 | impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { | |
66 | fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { | |
67 | if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) { | |
68 | return; | |
69 | } | |
70 | let mut unsafe_ops = vec![]; | |
71 | collect_unsafe_exprs(cx, block, &mut unsafe_ops); | |
72 | if unsafe_ops.len() > 1 { | |
73 | span_lint_and_then( | |
74 | cx, | |
75 | MULTIPLE_UNSAFE_OPS_PER_BLOCK, | |
76 | block.span, | |
77 | &format!( | |
78 | "this `unsafe` block contains {} unsafe operations, expected only one", | |
79 | unsafe_ops.len() | |
80 | ), | |
81 | |diag| { | |
82 | for (msg, span) in unsafe_ops { | |
83 | diag.span_note(span, msg); | |
84 | } | |
85 | }, | |
86 | ); | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | fn collect_unsafe_exprs<'tcx>( | |
92 | cx: &LateContext<'tcx>, | |
93 | node: impl Visitable<'tcx>, | |
94 | unsafe_ops: &mut Vec<(&'static str, Span)>, | |
95 | ) { | |
96 | for_each_expr_with_closures(cx, node, |expr| { | |
97 | match expr.kind { | |
98 | ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)), | |
99 | ||
100 | ExprKind::Field(e, _) => { | |
101 | if cx.typeck_results().expr_ty(e).is_union() { | |
102 | unsafe_ops.push(("union field access occurs here", expr.span)); | |
103 | } | |
104 | }, | |
105 | ||
106 | ExprKind::Path(QPath::Resolved( | |
107 | _, | |
108 | hir::Path { | |
109 | res: Res::Def(DefKind::Static(Mutability::Mut), _), | |
110 | .. | |
111 | }, | |
112 | )) => { | |
113 | unsafe_ops.push(("access of a mutable static occurs here", expr.span)); | |
114 | }, | |
115 | ||
116 | ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => { | |
117 | unsafe_ops.push(("raw pointer dereference occurs here", expr.span)); | |
118 | }, | |
119 | ||
353b0b11 FG |
120 | ExprKind::Call(path_expr, _) => { |
121 | let sig = match *cx.typeck_results().expr_ty(path_expr).kind() { | |
122 | ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(), | |
123 | ty::FnPtr(sig) => sig, | |
124 | _ => return Continue(Descend::Yes), | |
125 | }; | |
126 | if sig.unsafety() == Unsafety::Unsafe { | |
127 | unsafe_ops.push(("unsafe function call occurs here", expr.span)); | |
128 | } | |
9ffffee4 FG |
129 | }, |
130 | ||
131 | ExprKind::MethodCall(..) => { | |
132 | if let Some(sig) = cx | |
133 | .typeck_results() | |
134 | .type_dependent_def_id(expr.hir_id) | |
135 | .map(|def_id| cx.tcx.fn_sig(def_id)) | |
136 | { | |
fe692bf9 | 137 | if sig.skip_binder().unsafety() == Unsafety::Unsafe { |
9ffffee4 FG |
138 | unsafe_ops.push(("unsafe method call occurs here", expr.span)); |
139 | } | |
140 | } | |
141 | }, | |
142 | ||
143 | ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => { | |
144 | if matches!( | |
145 | lhs.kind, | |
146 | ExprKind::Path(QPath::Resolved( | |
147 | _, | |
148 | hir::Path { | |
149 | res: Res::Def(DefKind::Static(Mutability::Mut), _), | |
150 | .. | |
151 | } | |
152 | )) | |
153 | ) { | |
154 | unsafe_ops.push(("modification of a mutable static occurs here", expr.span)); | |
155 | collect_unsafe_exprs(cx, rhs, unsafe_ops); | |
156 | return Continue(Descend::No); | |
157 | } | |
158 | }, | |
159 | ||
160 | _ => {}, | |
161 | }; | |
162 | ||
163 | Continue::<(), _>(Descend::Yes) | |
164 | }); | |
165 | } |