diff options
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs new file mode 100644 index 000000000..63c575fca --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -0,0 +1,186 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, + visitors::{for_each_expr_with_closures, Descend, Visitable}, +}; +use core::ops::ControlFlow::Continue; +use hir::{ + def::{DefKind, Res}, + BlockCheckMode, ExprKind, QPath, UnOp, Unsafety, +}; +use rustc_ast::Mutability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `unsafe` blocks that contain more than one unsafe operation. + /// + /// ### Why is this bad? + /// Combined with `undocumented_unsafe_blocks`, + /// this lint ensures that each unsafe operation must be independently justified. + /// Combined with `unused_unsafe`, this lint also ensures + /// elimination of unnecessary unsafe blocks through refactoring. + /// + /// ### Example + /// ```rust + /// /// Reads a `char` from the given pointer. + /// /// + /// /// # Safety + /// /// + /// /// `ptr` must point to four consecutive, initialized bytes which + /// /// form a valid `char` when interpreted in the native byte order. + /// fn read_char(ptr: *const u8) -> char { + /// // SAFETY: The caller has guaranteed that the value pointed + /// // to by `bytes` is a valid `char`. + /// unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) } + /// } + /// ``` + /// Use instead: + /// ```rust + /// /// Reads a `char` from the given pointer. + /// /// + /// /// # Safety + /// /// + /// /// - `ptr` must be 4-byte aligned, point to four consecutive + /// /// initialized bytes, and be valid for reads of 4 bytes. + /// /// - The bytes pointed to by `ptr` must represent a valid + /// /// `char` when interpreted in the native byte order. + /// fn read_char(ptr: *const u8) -> char { + /// // SAFETY: `ptr` is 4-byte aligned, points to four consecutive + /// // initialized bytes, and is valid for reads of 4 bytes. + /// let int_value = unsafe { *ptr.cast::<u32>() }; + /// + /// // SAFETY: The caller has guaranteed that the four bytes + /// // pointed to by `bytes` represent a valid `char`. + /// unsafe { char::from_u32_unchecked(int_value) } + /// } + /// ``` + #[clippy::version = "1.68.0"] + pub MULTIPLE_UNSAFE_OPS_PER_BLOCK, + restriction, + "more than one unsafe operation per `unsafe` block" +} +declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]); + +impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { + if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) { + return; + } + let mut unsafe_ops = vec![]; + collect_unsafe_exprs(cx, block, &mut unsafe_ops); + if unsafe_ops.len() > 1 { + span_lint_and_then( + cx, + MULTIPLE_UNSAFE_OPS_PER_BLOCK, + block.span, + &format!( + "this `unsafe` block contains {} unsafe operations, expected only one", + unsafe_ops.len() + ), + |diag| { + for (msg, span) in unsafe_ops { + diag.span_note(span, msg); + } + }, + ); + } + } +} + +fn collect_unsafe_exprs<'tcx>( + cx: &LateContext<'tcx>, + node: impl Visitable<'tcx>, + unsafe_ops: &mut Vec<(&'static str, Span)>, +) { + for_each_expr_with_closures(cx, node, |expr| { + match expr.kind { + ExprKind::InlineAsm(_) => unsafe_ops.push(("inline assembly used here", expr.span)), + + ExprKind::Field(e, _) => { + if cx.typeck_results().expr_ty(e).is_union() { + unsafe_ops.push(("union field access occurs here", expr.span)); + } + }, + + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(DefKind::Static(Mutability::Mut), _), + .. + }, + )) => { + unsafe_ops.push(("access of a mutable static occurs here", expr.span)); + }, + + ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty_adjusted(e).is_unsafe_ptr() => { + unsafe_ops.push(("raw pointer dereference occurs here", expr.span)); + }, + + ExprKind::Call(path_expr, _) => match path_expr.kind { + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(kind, def_id), + .. + }, + )) if kind.is_fn_like() => { + let sig = cx.tcx.fn_sig(*def_id); + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe function call occurs here", expr.span)); + } + }, + + ExprKind::Path(QPath::TypeRelative(..)) => { + if let Some(sig) = cx + .typeck_results() + .type_dependent_def_id(path_expr.hir_id) + .map(|def_id| cx.tcx.fn_sig(def_id)) + { + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe function call occurs here", expr.span)); + } + } + }, + + _ => {}, + }, + + ExprKind::MethodCall(..) => { + if let Some(sig) = cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .map(|def_id| cx.tcx.fn_sig(def_id)) + { + if sig.0.unsafety() == Unsafety::Unsafe { + unsafe_ops.push(("unsafe method call occurs here", expr.span)); + } + } + }, + + ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => { + if matches!( + lhs.kind, + ExprKind::Path(QPath::Resolved( + _, + hir::Path { + res: Res::Def(DefKind::Static(Mutability::Mut), _), + .. + } + )) + ) { + unsafe_ops.push(("modification of a mutable static occurs here", expr.span)); + collect_unsafe_exprs(cx, rhs, unsafe_ops); + return Continue(Descend::No); + } + }, + + _ => {}, + }; + + Continue::<(), _>(Descend::Yes) + }); +} |