Skip to content

Add multiple_unsafe_ops_per_block lint #10206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4383,6 +4383,7 @@ Released 2018-09-13
[`multi_assignments`]: https://rust-lang.github.io/rust-clippy/master/index.html#multi_assignments
[`multiple_crate_versions`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_crate_versions
[`multiple_inherent_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl
[`multiple_unsafe_ops_per_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block
[`must_use_candidate`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate
[`must_use_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#must_use_unit
[`mut_from_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_from_ref
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::module_style::MOD_MODULE_FILES_INFO,
crate::module_style::SELF_NAMED_MODULE_FILES_INFO,
crate::multi_assignments::MULTI_ASSIGNMENTS_INFO,
crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
crate::mut_key::MUTABLE_KEY_TYPE_INFO,
crate::mut_mut::MUT_MUT_INFO,
crate::mut_reference::UNNECESSARY_MUT_PASSED_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ mod missing_trait_methods;
mod mixed_read_write_in_expression;
mod module_style;
mod multi_assignments;
mod multiple_unsafe_ops_per_block;
mod mut_key;
mod mut_mut;
mod mut_reference;
Expand Down Expand Up @@ -908,6 +909,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(fn_null_check::FnNullCheck));
store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse));
store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef));
store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock));
// add lints here, do not remove this comment, it's used in `new_lint`
}

Expand Down
185 changes: 185 additions & 0 deletions clippy_lints/src/multiple_unsafe_ops_per_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
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_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(_)) {
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.bound_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.bound_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.bound_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)
});
}
110 changes: 110 additions & 0 deletions tests/ui/multiple_unsafe_ops_per_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#![allow(unused)]
#![allow(deref_nullptr)]
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::drop_copy)]
#![warn(clippy::multiple_unsafe_ops_per_block)]

use core::arch::asm;

fn raw_ptr() -> *const () {
core::ptr::null()
}

unsafe fn not_very_safe() {}

struct Sample;

impl Sample {
unsafe fn not_very_safe(&self) {}
}

#[allow(non_upper_case_globals)]
const sample: Sample = Sample;

union U {
i: i32,
u: u32,
}

static mut STATIC: i32 = 0;

fn test1() {
unsafe {
STATIC += 1;
not_very_safe();
}
}

fn test2() {
let u = U { i: 0 };

unsafe {
drop(u.u);
*raw_ptr();
}
}

fn test3() {
unsafe {
asm!("nop");
sample.not_very_safe();
STATIC = 0;
}
}

fn test_all() {
let u = U { i: 0 };
unsafe {
drop(u.u);
drop(STATIC);
sample.not_very_safe();
not_very_safe();
*raw_ptr();
asm!("nop");
}
}

// no lint
fn correct1() {
unsafe {
STATIC += 1;
}
}

// no lint
fn correct2() {
unsafe {
STATIC += 1;
}

unsafe {
*raw_ptr();
}
}

// no lint
fn correct3() {
let u = U { u: 0 };

unsafe {
not_very_safe();
}

unsafe {
drop(u.i);
}
}

// tests from the issue (https://github.com/rust-lang/rust-clippy/issues/10064)

unsafe fn read_char_bad(ptr: *const u8) -> char {
unsafe { char::from_u32_unchecked(*ptr.cast::<u32>()) }
}

// no lint
unsafe fn read_char_good(ptr: *const u8) -> char {
let int_value = unsafe { *ptr.cast::<u32>() };
unsafe { core::char::from_u32_unchecked(int_value) }
}

fn main() {}
Loading