169 lines
6.2 KiB
Rust
169 lines
6.2 KiB
Rust
use clippy_utils::diagnostics::span_lint_and_then;
|
|
use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable};
|
|
use core::ops::ControlFlow::Continue;
|
|
use hir::def::{DefKind, Res};
|
|
use hir::{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_middle::ty;
|
|
use rustc_session::declare_lint_pass;
|
|
use rustc_span::{DesugaringKind, 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
|
|
/// ```no_run
|
|
/// /// 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:
|
|
/// ```no_run
|
|
/// /// 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.69.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)
|
|
|| block.span.is_desugaring(DesugaringKind::Await)
|
|
{
|
|
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, _) => {
|
|
let sig = match *cx.typeck_results().expr_ty(path_expr).kind() {
|
|
ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(),
|
|
ty::FnPtr(sig) => sig,
|
|
_ => return Continue(Descend::Yes),
|
|
};
|
|
if sig.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.skip_binder().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)
|
|
});
|
|
}
|