2 diagnostics::span_lint_and_then,
3 visitors::{for_each_expr_with_closures, Descend, Visitable},
5 use core::ops::ControlFlow::Continue;
8 BlockCheckMode, ExprKind, QPath, UnOp, Unsafety,
10 use rustc_ast::Mutability;
12 use rustc_lint::{LateContext, LateLintPass};
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
16 declare_clippy_lint! {
18 /// Checks for `unsafe` blocks that contain more than one unsafe operation.
20 /// ### Why is this bad?
21 /// Combined with `undocumented_unsafe_blocks`,
22 /// this lint ensures that each unsafe operation must be independently justified.
23 /// Combined with `unused_unsafe`, this lint also ensures
24 /// elimination of unnecessary unsafe blocks through refactoring.
28 /// /// Reads a `char` from the given pointer.
32 /// /// `ptr` must point to four consecutive, initialized bytes which
33 /// /// form a valid `char` when interpreted in the native byte order.
34 /// fn read_char(ptr: *const u8) -> char {
35 /// // SAFETY: The caller has guaranteed that the value pointed
36 /// // to by `bytes` is a valid `char`.
37 /// unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
42 /// /// Reads a `char` from the given pointer.
46 /// /// - `ptr` must be 4-byte aligned, point to four consecutive
47 /// /// initialized bytes, and be valid for reads of 4 bytes.
48 /// /// - The bytes pointed to by `ptr` must represent a valid
49 /// /// `char` when interpreted in the native byte order.
50 /// fn read_char(ptr: *const u8) -> char {
51 /// // SAFETY: `ptr` is 4-byte aligned, points to four consecutive
52 /// // initialized bytes, and is valid for reads of 4 bytes.
53 /// let int_value = unsafe { *ptr.cast::<u32>() };
55 /// // SAFETY: The caller has guaranteed that the four bytes
56 /// // pointed to by `bytes` represent a valid `char`.
57 /// unsafe { char::from_u32_unchecked(int_value) }
60 #[clippy::version = "1.68.0"]
61 pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
63 "more than one unsafe operation per `unsafe` block"
65 declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);
67 impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
68 fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
69 if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) {
72 let mut unsafe_ops = vec![];
73 collect_unsafe_exprs(cx, block, &mut unsafe_ops);
74 if unsafe_ops.len() > 1 {
77 MULTIPLE_UNSAFE_OPS_PER_BLOCK,
80 "this `unsafe` block contains {} unsafe operations, expected only one",
84 for (msg, span) in unsafe_ops {
85 diag.span_note(span, msg);
93 fn collect_unsafe_exprs<'tcx>(
94 cx: &LateContext<'tcx>,
95 node: impl Visitable<'tcx>,
96 unsafe_ops: &mut Vec<(&'static str, Span)>,
98 for_each_expr_with_closures(cx, node, |expr| {
100 ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)),
102 ExprKind::Field(e, _) => {
103 if cx.typeck_results().expr_ty(e).is_union() {
104 unsafe_ops.push(("union field access occurs here", expr.span));
108 ExprKind::Path(QPath::Resolved(
111 res: Res::Def(DefKind::Static(Mutability::Mut), _),
115 unsafe_ops.push(("access of a mutable static occurs here", expr.span));
118 ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => {
119 unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
122 ExprKind::Call(path_expr, _) => match path_expr.kind {
123 ExprKind::Path(QPath::Resolved(
126 res: Res::Def(kind, def_id),
129 )) if kind.is_fn_like() => {
130 let sig = cx.tcx.fn_sig(*def_id);
131 if sig.0.unsafety() == Unsafety::Unsafe {
132 unsafe_ops.push(("unsafe function call occurs here", expr.span));
136 ExprKind::Path(QPath::TypeRelative(..)) => {
137 if let Some(sig) = cx
139 .type_dependent_def_id(path_expr.hir_id)
140 .map(|def_id| cx.tcx.fn_sig(def_id))
142 if sig.0.unsafety() == Unsafety::Unsafe {
143 unsafe_ops.push(("unsafe function call occurs here", expr.span));
151 ExprKind::MethodCall(..) => {
152 if let Some(sig) = cx
154 .type_dependent_def_id(expr.hir_id)
155 .map(|def_id| cx.tcx.fn_sig(def_id))
157 if sig.0.unsafety() == Unsafety::Unsafe {
158 unsafe_ops.push(("unsafe method call occurs here", expr.span));
163 ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
166 ExprKind::Path(QPath::Resolved(
169 res: Res::Def(DefKind::Static(Mutability::Mut), _),
174 unsafe_ops.push(("modification of a mutable static occurs here", expr.span));
175 collect_unsafe_exprs(cx, rhs, unsafe_ops);
176 return Continue(Descend::No);
183 Continue::<(), _>(Descend::Yes)