Skip to content

Commit da703dd

Browse files
committed
Address PR comments
1 parent 4c77549 commit da703dd

File tree

12 files changed

+230
-99
lines changed

12 files changed

+230
-99
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5297,7 +5297,6 @@ Released 2018-09-13
52975297
[`explicit_into_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_into_iter_loop
52985298
[`explicit_iter_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop
52995299
[`explicit_write`]: https://rust-lang.github.io/rust-clippy/master/index.html#explicit_write
5300-
[`expr_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#expr_metavars_in_unsafe
53015300
[`extend_from_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_from_slice
53025301
[`extend_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#extend_with_drain
53035302
[`extra_unused_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_lifetimes
@@ -5448,6 +5447,7 @@ Released 2018-09-13
54485447
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
54495448
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
54505449
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
5450+
[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe
54515451
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
54525452
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
54535453
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
@@ -6003,4 +6003,5 @@ Released 2018-09-13
60036003
[`vec-box-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#vec-box-size-threshold
60046004
[`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold
60056005
[`warn-on-all-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-on-all-wildcard-imports
6006+
[`warn-unsafe-macro-metavars-in-private-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-unsafe-macro-metavars-in-private-macros
60066007
<!-- end autogenerated links to configuration documentation -->

book/src/lint_configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,13 @@ Whether to allow certain wildcard imports (prelude, super in tests).
900900
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
901901

902902

903+
## `warn-unsafe-macro-metavars-in-private-macros`
904+
Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
905+
906+
**Default Value:** `false`
907+
908+
---
909+
**Affected lints:**
910+
* [`macro_metavars_in_unsafe`](https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe)
911+
912+

clippy_config/src/conf.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,10 @@ define_Conf! {
613613
/// - Use `".."` as part of the list to indicate that the configured values should be appended to the
614614
/// default configuration of Clippy. By default, any configuration will replace the default value
615615
(allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()),
616+
/// Lint: MACRO_METAVARS_IN_UNSAFE.
617+
///
618+
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
619+
(warn_unsafe_macro_metavars_in_private_macros: bool = false),
616620
}
617621

618622
/// Search for the configuration file.

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
177177
crate::exhaustive_items::EXHAUSTIVE_STRUCTS_INFO,
178178
crate::exit::EXIT_INFO,
179179
crate::explicit_write::EXPLICIT_WRITE_INFO,
180-
crate::expr_metavars_in_unsafe::EXPR_METAVARS_IN_UNSAFE_INFO,
181180
crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO,
182181
crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO,
183182
crate::float_literal::EXCESSIVE_PRECISION_INFO,
@@ -295,6 +294,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
295294
crate::loops::WHILE_IMMUTABLE_CONDITION_INFO,
296295
crate::loops::WHILE_LET_LOOP_INFO,
297296
crate::loops::WHILE_LET_ON_ITERATOR_INFO,
297+
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
298298
crate::macro_use::MACRO_USE_IMPORTS_INFO,
299299
crate::main_recursion::MAIN_RECURSION_INFO,
300300
crate::manual_assert::MANUAL_ASSERT_INFO,

clippy_lints/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ mod excessive_nesting;
133133
mod exhaustive_items;
134134
mod exit;
135135
mod explicit_write;
136-
mod expr_metavars_in_unsafe;
137136
mod extra_unused_type_parameters;
138137
mod fallible_impl_from;
139138
mod float_literal;
@@ -194,6 +193,7 @@ mod lifetimes;
194193
mod lines_filter_map_ok;
195194
mod literal_representation;
196195
mod loops;
196+
mod macro_metavars_in_unsafe;
197197
mod macro_use;
198198
mod main_recursion;
199199
mod manual_assert;
@@ -599,6 +599,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
599599

600600
blacklisted_names: _,
601601
cyclomatic_complexity_threshold: _,
602+
warn_unsafe_macro_metavars_in_private_macros,
602603
} = *conf;
603604
let msrv = || msrv.clone();
604605

@@ -1154,7 +1155,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
11541155
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
11551156
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
11561157
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
1157-
store.register_late_pass(|_| Box::<expr_metavars_in_unsafe::ExprMetavarsInUnsafe>::default());
1158+
store.register_late_pass(move |_| {
1159+
Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe {
1160+
warn_unsafe_macro_metavars_in_private_macros,
1161+
..Default::default()
1162+
})
1163+
});
11581164
// add lints here, do not remove this comment, it's used in `new_lint`
11591165
}
11601166

clippy_lints/src/expr_metavars_in_unsafe.rs renamed to clippy_lints/src/macro_metavars_in_unsafe.rs

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,40 @@ use std::collections::BTreeMap;
44
use clippy_utils::diagnostics::span_lint_hir_and_then;
55
use clippy_utils::is_lint_allowed;
66
use itertools::Itertools;
7+
use rustc_hir::def_id::LocalDefId;
78
use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor};
89
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource};
910
use rustc_lint::{LateContext, LateLintPass};
1011
use rustc_session::impl_lint_pass;
11-
use rustc_span::def_id::DefId;
1212
use rustc_span::{sym, Span, SyntaxContext};
1313

1414
declare_clippy_lint! {
1515
/// ### What it does
1616
/// Looks for macros that expand metavariables in an unsafe block.
1717
///
1818
/// ### Why is this bad?
19-
/// This is unsound: it allows the user of the macro to write unsafe code outside of an
20-
/// unsafe block at callsite, potentially invoking undefined behavior in safe code.
19+
/// This hides an unsafe block and allows the user of the macro to write unsafe code without an explicit
20+
/// unsafe block at callsite, making it possible to perform unsafe operations in seemingly safe code.
21+
///
22+
/// The macro should be restructured so that these metavariables are referenced outside of unsafe blocks
23+
/// and that the usual unsafety checks apply to the macro argument.
24+
///
25+
/// This is usually done by binding it to a variable outside of the unsafe block
26+
/// and then using that variable inside of the block as shown in the example, or by referencing it a second time
27+
/// in a safe context, e.g. `if false { $expr }`.
2128
///
2229
/// ### Known limitations
2330
/// Due to how macros are represented in the compiler at the time Clippy runs its lints,
2431
/// it's not possible to look for metavariables in macro definitions directly.
2532
///
26-
/// Instead, this lint looks at expansions of macros defined in the same crate.
27-
/// This leads to false negatives when a macro is never actually invoked.
33+
/// Instead, this lint looks at expansions of macros.
34+
/// This leads to false negatives for macros that are never actually invoked.
35+
///
36+
/// By default, this lint is rather conservative and will only emit warnings on publicly-exported
37+
/// macros from the same crate, because oftentimes private internal macros are one-off macros where
38+
/// this lint would just be noise (e.g. macros that generate `impl` blocks).
39+
/// The default behavior should help with preventing a high number of such false positives,
40+
/// however it can be configured to also emit warnings in private macros if desired.
2841
///
2942
/// ### Example
3043
/// ```no_run
@@ -65,19 +78,21 @@ declare_clippy_lint! {
6578
/// assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1);
6679
/// ```
6780
#[clippy::version = "1.77.0"]
68-
pub EXPR_METAVARS_IN_UNSAFE,
69-
nursery,
70-
"expanding expr metavariables in an unsafe block"
81+
pub MACRO_METAVARS_IN_UNSAFE,
82+
suspicious,
83+
"expanding macro metavariables in an unsafe block"
7184
}
85+
impl_lint_pass!(ExprMetavarsInUnsafe => [MACRO_METAVARS_IN_UNSAFE]);
7286

7387
#[derive(Clone, Debug)]
74-
enum MetavarState {
88+
pub enum MetavarState {
7589
ReferencedInUnsafe { unsafe_blocks: Vec<HirId> },
7690
ReferencedInSafe,
7791
}
7892

7993
#[derive(Default)]
8094
pub struct ExprMetavarsInUnsafe {
95+
pub warn_unsafe_macro_metavars_in_private_macros: bool,
8196
/// A metavariable can be expanded more than once, potentially across multiple bodies, so it
8297
/// requires some state kept across HIR nodes to make it possible to delay a warning
8398
/// and later undo:
@@ -91,21 +106,27 @@ pub struct ExprMetavarsInUnsafe {
91106
/// }
92107
/// }
93108
/// ```
94-
metavar_expns: BTreeMap<Span, MetavarState>,
109+
pub metavar_expns: BTreeMap<Span, MetavarState>,
95110
}
96-
impl_lint_pass!(ExprMetavarsInUnsafe => [EXPR_METAVARS_IN_UNSAFE]);
97111

98-
struct BodyVisitor<'a> {
99-
/// The top item always represents the last seen unsafe block
112+
struct BodyVisitor<'a, 'tcx> {
113+
/// Stack of unsafe blocks -- the top item always represents the last seen unsafe block from
114+
/// within a relevant macro.
100115
macro_unsafe_blocks: Vec<HirId>,
101-
/// When this is >0, it means that the node in the visitor currently being visited is "within" a
102-
/// macro definition. This helps reduce the number of spans we need to insert into the map,
103-
/// since only spans from macros are relevant.
116+
/// When this is >0, it means that the node currently being visited is "within" a
117+
/// macro definition. This is not necessary for correctness, it merely helps reduce the number
118+
/// of spans we need to insert into the map, since only spans from macros are relevant.
104119
expn_depth: u32,
105-
metavar_map: &'a mut BTreeMap<Span, MetavarState>,
120+
cx: &'a LateContext<'tcx>,
121+
lint: &'a mut ExprMetavarsInUnsafe,
122+
}
123+
124+
fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
125+
(cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export))
126+
&& !cx.tcx.is_doc_hidden(def_id)
106127
}
107128

108-
impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a> {
129+
impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> {
109130
fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
110131
let from_expn = s.span.from_expansion();
111132
if from_expn {
@@ -123,15 +144,17 @@ impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a> {
123144
if let ExprKind::Block(block, _) = e.kind
124145
&& let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules
125146
&& !ctxt.is_root()
126-
&& ctxt.outer_expn_data().macro_def_id.is_some_and(DefId::is_local)
147+
&& let Some(macro_def_id) = ctxt.outer_expn_data().macro_def_id
148+
&& let Some(macro_def_id) = macro_def_id.as_local()
149+
&& (self.lint.warn_unsafe_macro_metavars_in_private_macros || is_public_macro(self.cx, macro_def_id))
127150
{
128151
self.macro_unsafe_blocks.push(block.hir_id);
129152
walk_block(self, block);
130153
self.macro_unsafe_blocks.pop();
131154
} else if ctxt.is_root() && self.expn_depth > 0 {
132155
let unsafe_block = self.macro_unsafe_blocks.last().copied();
133156

134-
match (self.metavar_map.entry(e.span), unsafe_block) {
157+
match (self.lint.metavar_expns.entry(e.span), unsafe_block) {
135158
(Entry::Vacant(e), None) => {
136159
e.insert(MetavarState::ReferencedInSafe);
137160
},
@@ -164,7 +187,7 @@ impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a> {
164187

165188
impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
166189
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx rustc_hir::Body<'tcx>) {
167-
if is_lint_allowed(cx, EXPR_METAVARS_IN_UNSAFE, body.value.hir_id) {
190+
if is_lint_allowed(cx, MACRO_METAVARS_IN_UNSAFE, body.value.hir_id) {
168191
return;
169192
}
170193

@@ -175,7 +198,8 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
175198
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
176199
expn_depth: if body.value.span.from_expansion() { 1 } else { 0 },
177200
macro_unsafe_blocks: Vec::new(),
178-
metavar_map: &mut self.metavar_expns,
201+
lint: self,
202+
cx
179203
};
180204
vis.visit_body(body);
181205
}
@@ -205,37 +229,28 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
205229
// The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything
206230
// related to the callsite.
207231
let span = cx.tcx.hir().span(id);
208-
let macro_def_id = span.ctxt().outer_expn_data().macro_def_id.and_then(DefId::as_local);
209-
(
210-
id,
211-
Span::new(span.lo(), span.hi(), SyntaxContext::root(), None),
212-
macro_def_id,
213-
)
232+
233+
(id, Span::new(span.lo(), span.hi(), SyntaxContext::root(), None))
214234
})
215-
.dedup_by(|(_, a, _), (_, b, _)| a == b);
216-
217-
for (id, span, def_id) in bad_unsafe_blocks {
218-
if let Some(def_id) = def_id
219-
&& (cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export))
220-
&& !cx.tcx.is_doc_hidden(def_id)
221-
{
222-
span_lint_hir_and_then(
223-
cx,
224-
EXPR_METAVARS_IN_UNSAFE,
225-
id,
226-
span,
227-
"this unsafe block in a macro expands `expr` metavariables",
228-
|diag| {
229-
diag.note("this allows the user of the macro to write unsafe code outside of an unsafe block");
230-
diag.help(
235+
.dedup_by(|&(_, a), &(_, b)| a == b);
236+
237+
for (id, span) in bad_unsafe_blocks {
238+
span_lint_hir_and_then(
239+
cx,
240+
MACRO_METAVARS_IN_UNSAFE,
241+
id,
242+
span,
243+
"this unsafe block in a macro expands metavariables",
244+
|diag| {
245+
diag.note("this allows the user of the macro to write unsafe code outside of an unsafe block");
246+
diag.help(
231247
"consider expanding any metavariables outside of this block, e.g. by storing them in a variable",
232248
);
233-
diag.help(
249+
diag.help(
234250
"... or also expand referenced metavariables in a safe context to require an unsafe block at callsite",
235251
);
236-
},
237-
);
238-
}
252+
},
253+
);
239254
}
240255
}
241256
}

0 commit comments

Comments
 (0)