Skip to content

Commit 6161ac1

Browse files
committed
Still lint raw pointer to union field if MSRV < 1.92
1 parent 24e16f9 commit 6161ac1

File tree

7 files changed

+154
-86
lines changed

7 files changed

+154
-86
lines changed

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
892892
* [`mem_replace_option_with_some`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_option_with_some)
893893
* [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default)
894894
* [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn)
895+
* [`multiple_unsafe_ops_per_block`](https://rust-lang.github.io/rust-clippy/master/index.html#multiple_unsafe_ops_per_block)
895896
* [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow)
896897
* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics)
897898
* [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref)

clippy_config/src/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ define_Conf! {
781781
mem_replace_option_with_some,
782782
mem_replace_with_default,
783783
missing_const_for_fn,
784+
multiple_unsafe_ops_per_block,
784785
needless_borrow,
785786
non_std_lazy_statics,
786787
option_as_ref_deref,

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
765765
Box::new(move |_| Box::new(semicolon_block::SemicolonBlock::new(conf))),
766766
Box::new(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)),
767767
Box::new(|_| Box::new(size_of_ref::SizeOfRef)),
768-
Box::new(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)),
768+
Box::new(move |_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock::new(conf))),
769769
Box::new(move |_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new(conf))),
770770
Box::new(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)),
771771
Box::new(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)),

clippy_lints/src/multiple_unsafe_ops_per_block.rs

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
use clippy_utils::desugar_await;
1+
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::msrvs::Msrv;
4+
use clippy_utils::{desugar_await, msrvs};
35
use hir::def::{DefKind, Res};
46
use hir::{BlockCheckMode, ExprKind, QPath, UnOp};
57
use rustc_ast::{BorrowKind, Mutability};
68
use rustc_hir as hir;
79
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
810
use rustc_lint::{LateContext, LateLintPass};
911
use rustc_middle::hir::nested_filter;
10-
use rustc_middle::ty::{self, TyCtxt, TypeckResults};
11-
use rustc_session::declare_lint_pass;
12+
use rustc_middle::ty::{self, TypeckResults};
13+
use rustc_session::impl_lint_pass;
1214
use rustc_span::{DesugaringKind, Span};
1315

1416
declare_clippy_lint! {
@@ -55,19 +57,23 @@ declare_clippy_lint! {
5557
/// unsafe { char::from_u32_unchecked(int_value) }
5658
/// }
5759
/// ```
58-
///
59-
/// ### Note
60-
///
61-
/// Taking a raw pointer to a union field is always safe and will
62-
/// not be considered unsafe by this lint, even when linting code written
63-
/// with a specified Rust version of 1.91 or earlier (which required
64-
/// using an `unsafe` block).
6560
#[clippy::version = "1.69.0"]
6661
pub MULTIPLE_UNSAFE_OPS_PER_BLOCK,
6762
restriction,
6863
"more than one unsafe operation per `unsafe` block"
6964
}
70-
declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);
65+
66+
pub struct MultipleUnsafeOpsPerBlock {
67+
msrv: Msrv,
68+
}
69+
70+
impl_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK]);
71+
72+
impl MultipleUnsafeOpsPerBlock {
73+
pub fn new(conf: &Conf) -> Self {
74+
Self { msrv: conf.msrv }
75+
}
76+
}
7177

7278
impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
7379
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
@@ -77,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
7783
{
7884
return;
7985
}
80-
let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block);
86+
let unsafe_ops = UnsafeExprCollector::collect_unsafe_exprs(cx, block, self.msrv);
8187
if unsafe_ops.len() > 1 {
8288
span_lint_and_then(
8389
cx,
@@ -97,28 +103,52 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
97103
}
98104
}
99105

100-
struct UnsafeExprCollector<'tcx> {
101-
tcx: TyCtxt<'tcx>,
106+
#[derive(Clone, Copy)]
107+
enum UnderRawPtr {
108+
/// The expression is not located under a raw pointer
109+
No,
110+
/// The expression is located under a raw pointer, MSRV yet unknown
111+
Yes,
112+
/// The expression is located under a raw pointer and MSRV has been determined.
113+
/// `true` means that taking a raw pointer to a union field is a safe operation.
114+
WithSafeMsrv(bool),
115+
}
116+
117+
struct UnsafeExprCollector<'cx, 'tcx> {
118+
cx: &'cx LateContext<'tcx>,
102119
typeck_results: &'tcx TypeckResults<'tcx>,
120+
msrv: Msrv,
103121
unsafe_ops: Vec<(&'static str, Span)>,
122+
under_raw_ptr: UnderRawPtr,
104123
}
105124

106-
impl<'tcx> UnsafeExprCollector<'tcx> {
107-
fn collect_unsafe_exprs(cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) -> Vec<(&'static str, Span)> {
125+
impl<'cx, 'tcx> UnsafeExprCollector<'cx, 'tcx> {
126+
fn collect_unsafe_exprs(
127+
cx: &'cx LateContext<'tcx>,
128+
block: &'tcx hir::Block<'tcx>,
129+
msrv: Msrv,
130+
) -> Vec<(&'static str, Span)> {
108131
let mut collector = Self {
109-
tcx: cx.tcx,
132+
cx,
110133
typeck_results: cx.typeck_results(),
134+
msrv,
111135
unsafe_ops: vec![],
136+
under_raw_ptr: UnderRawPtr::No,
112137
};
113138
collector.visit_block(block);
114139
collector.unsafe_ops
115140
}
116141
}
117142

118-
impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
143+
impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'_, 'tcx> {
119144
type NestedFilter = nested_filter::OnlyBodies;
120145

121146
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
147+
// `self.under_raw_ptr` is preventively reset, while the current value is
148+
// preserved in `under_raw_ptr`.
149+
let under_raw_ptr = self.under_raw_ptr;
150+
self.under_raw_ptr = UnderRawPtr::No;
151+
122152
match expr.kind {
123153
// The `await` itself will desugar to two unsafe calls, but we should ignore those.
124154
// Instead, check the expression that is `await`ed
@@ -128,17 +158,21 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
128158

129159
ExprKind::InlineAsm(_) => self.unsafe_ops.push(("inline assembly used here", expr.span)),
130160

131-
ExprKind::AddrOf(BorrowKind::Raw, _, mut inner) => {
132-
while let ExprKind::Field(prefix, _) = inner.kind
133-
&& self.typeck_results.expr_adjustments(prefix).is_empty()
134-
{
135-
inner = prefix;
136-
}
137-
return self.visit_expr(inner);
161+
ExprKind::AddrOf(BorrowKind::Raw, _, _) => {
162+
self.under_raw_ptr = UnderRawPtr::Yes;
138163
},
139164

140-
ExprKind::Field(e, _) => {
141-
if self.typeck_results.expr_ty(e).is_union() {
165+
ExprKind::Field(e, _) if self.typeck_results.expr_adjustments(e).is_empty() => {
166+
// Restore `self.under_raw_pointer` and determine safety of taking a raw pointer to
167+
// a union field if this is not known already.
168+
self.under_raw_ptr = if matches!(under_raw_ptr, UnderRawPtr::Yes) {
169+
UnderRawPtr::WithSafeMsrv(self.msrv.meets(self.cx, msrvs::SAFE_RAW_PTR_TO_UNION_FIELD))
170+
} else {
171+
under_raw_ptr
172+
};
173+
if self.typeck_results.expr_ty(e).is_union()
174+
&& matches!(self.under_raw_ptr, UnderRawPtr::No | UnderRawPtr::WithSafeMsrv(false))
175+
{
142176
self.unsafe_ops.push(("union field access occurs here", expr.span));
143177
}
144178
},
@@ -167,7 +201,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
167201

168202
ExprKind::Call(path_expr, _) => {
169203
let opt_sig = match *self.typeck_results.expr_ty_adjusted(path_expr).kind() {
170-
ty::FnDef(id, _) => Some(self.tcx.fn_sig(id).skip_binder()),
204+
ty::FnDef(id, _) => Some(self.cx.tcx.fn_sig(id).skip_binder()),
171205
ty::FnPtr(sig_tys, hdr) => Some(sig_tys.with(hdr)),
172206
_ => None,
173207
};
@@ -180,7 +214,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
180214
let opt_sig = self
181215
.typeck_results
182216
.type_dependent_def_id(expr.hir_id)
183-
.map(|def_id| self.tcx.fn_sig(def_id));
217+
.map(|def_id| self.cx.tcx.fn_sig(def_id));
184218
if opt_sig.is_some_and(|sig| sig.skip_binder().safety().is_unsafe()) {
185219
self.unsafe_ops.push(("unsafe method call occurs here", expr.span));
186220
}
@@ -217,12 +251,12 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
217251

218252
fn visit_body(&mut self, body: &hir::Body<'tcx>) {
219253
let saved_typeck_results = self.typeck_results;
220-
self.typeck_results = self.tcx.typeck_body(body.id());
254+
self.typeck_results = self.cx.tcx.typeck_body(body.id());
221255
walk_body(self, body);
222256
self.typeck_results = saved_typeck_results;
223257
}
224258

225259
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
226-
self.tcx
260+
self.cx.tcx
227261
}
228262
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ macro_rules! msrv_aliases {
2323

2424
// names may refer to stabilized feature flags or library items
2525
msrv_aliases! {
26+
1,92,0 { SAFE_RAW_PTR_TO_UNION_FIELD }
2627
1,88,0 { LET_CHAINS }
2728
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
2829
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }

tests/ui/multiple_unsafe_ops_per_block.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@needs-asm-support
22
//@aux-build:proc_macros.rs
3+
#![feature(stmt_expr_attributes)]
34
#![expect(
45
dropping_copy_types,
56
clippy::unnecessary_operation,
@@ -227,11 +228,20 @@ fn issue16076() {
227228
let u = U { i: 0 };
228229

229230
// Taking a raw pointer to a place is safe since Rust 1.92
231+
#[clippy::msrv = "1.92"]
230232
unsafe {
231233
_ = &raw const u.i;
232234
_ = &raw const u.i;
233235
}
234236

237+
// However it was not the case before Rust 1.92
238+
#[clippy::msrv = "1.91"]
239+
unsafe {
240+
//~^ multiple_unsafe_ops_per_block
241+
_ = &raw const u.i;
242+
_ = &raw const u.i;
243+
}
244+
235245
// Taking a reference to a union field is not safe
236246
unsafe {
237247
//~^ multiple_unsafe_ops_per_block

0 commit comments

Comments
 (0)