@@ -3,13 +3,14 @@ use clippy_utils::diagnostics::span_lint_and_then;
33use hir:: def:: { DefKind , Res } ;
44use hir:: { BlockCheckMode , ExprKind , QPath , UnOp } ;
55use rustc_ast:: { BorrowKind , Mutability } ;
6+ use rustc_data_structures:: fx:: FxHashMap ;
67use rustc_hir as hir;
78use rustc_hir:: intravisit:: { Visitor , walk_body, walk_expr} ;
89use rustc_lint:: { LateContext , LateLintPass } ;
910use rustc_middle:: hir:: nested_filter;
1011use rustc_middle:: ty:: { self , TyCtxt , TypeckResults } ;
1112use rustc_session:: declare_lint_pass;
12- use rustc_span:: { DesugaringKind , Span } ;
13+ use rustc_span:: Span ;
1314
1415declare_clippy_lint ! {
1516 /// ### What it does
@@ -56,12 +57,16 @@ declare_clippy_lint! {
5657 /// }
5758 /// ```
5859 ///
59- /// ### Note
60+ /// ### Notes
6061 ///
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).
62+ /// - Unsafe operations only count towards the total for the innermost
63+ /// enclosing `unsafe` block.
64+ /// - Each call to a macro expanding to unsafe operations count for one
65+ /// unsafe operation.
66+ /// - Taking a raw pointer to a union field is always safe and will
67+ /// not be considered unsafe by this lint, even when linting code written
68+ /// with a specified Rust version of 1.91 or earlier (which required
69+ /// using an `unsafe` block).
6570 #[ clippy:: version = "1.69.0" ]
6671 pub MULTIPLE_UNSAFE_OPS_PER_BLOCK ,
6772 restriction,
@@ -71,10 +76,7 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK])
7176
7277impl < ' tcx > LateLintPass < ' tcx > for MultipleUnsafeOpsPerBlock {
7378 fn check_block ( & mut self , cx : & LateContext < ' tcx > , block : & ' tcx hir:: Block < ' _ > ) {
74- if !matches ! ( block. rules, BlockCheckMode :: UnsafeBlock ( _) )
75- || block. span . in_external_macro ( cx. tcx . sess . source_map ( ) )
76- || block. span . is_desugaring ( DesugaringKind :: Await )
77- {
79+ if !matches ! ( block. rules, BlockCheckMode :: UnsafeBlock ( _) ) || block. span . from_expansion ( ) {
7880 return ;
7981 }
8082 let unsafe_ops = UnsafeExprCollector :: collect_unsafe_exprs ( cx, block) ;
@@ -100,18 +102,41 @@ impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
100102struct UnsafeExprCollector < ' tcx > {
101103 tcx : TyCtxt < ' tcx > ,
102104 typeck_results : & ' tcx TypeckResults < ' tcx > ,
103- unsafe_ops : Vec < ( & ' static str , Span ) > ,
105+ unsafe_ops : FxHashMap < Span , & ' static str > ,
104106}
105107
106108impl < ' tcx > UnsafeExprCollector < ' tcx > {
107109 fn collect_unsafe_exprs ( cx : & LateContext < ' tcx > , block : & ' tcx hir:: Block < ' tcx > ) -> Vec < ( & ' static str , Span ) > {
108110 let mut collector = Self {
109111 tcx : cx. tcx ,
110112 typeck_results : cx. typeck_results ( ) ,
111- unsafe_ops : vec ! [ ] ,
113+ unsafe_ops : FxHashMap :: default ( ) ,
112114 } ;
113115 collector. visit_block ( block) ;
114- collector. unsafe_ops
116+ #[ allow(
117+ rustc:: potential_query_instability,
118+ reason = "span ordering only needed inside the one expression being walked"
119+ ) ]
120+ let mut unsafe_ops = collector
121+ . unsafe_ops
122+ . into_iter ( )
123+ . map ( |( span, msg) | ( msg, span) )
124+ . collect :: < Vec < _ > > ( ) ;
125+ unsafe_ops. sort_unstable ( ) ;
126+ unsafe_ops
127+ }
128+ }
129+
130+ impl UnsafeExprCollector < ' _ > {
131+ fn insert_span ( & mut self , span : Span , message : & ' static str ) {
132+ if span. from_expansion ( ) {
133+ self . unsafe_ops . insert (
134+ span. source_callsite ( ) ,
135+ "this macro call expands into one or more unsafe operations" ,
136+ ) ;
137+ } else {
138+ self . unsafe_ops . insert ( span, message) ;
139+ }
115140 }
116141}
117142
@@ -126,7 +151,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
126151 return self . visit_expr ( e) ;
127152 } ,
128153
129- ExprKind :: InlineAsm ( _) => self . unsafe_ops . push ( ( "inline assembly used here" , expr. span ) ) ,
154+ // Do not recurse inside an inner `unsafe` block, it will be checked on its own
155+ ExprKind :: Block ( block, _) if matches ! ( block. rules, BlockCheckMode :: UnsafeBlock ( _) ) => return ,
156+
157+ ExprKind :: InlineAsm ( _) => self . insert_span ( expr. span , "inline assembly used here" ) ,
130158
131159 ExprKind :: AddrOf ( BorrowKind :: Raw , _, mut inner) => {
132160 while let ExprKind :: Field ( prefix, _) = inner. kind
@@ -139,7 +167,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
139167
140168 ExprKind :: Field ( e, _) => {
141169 if self . typeck_results . expr_ty ( e) . is_union ( ) {
142- self . unsafe_ops . push ( ( "union field access occurs here" , expr . span ) ) ;
170+ self . insert_span ( expr . span , "union field access occurs here" ) ;
143171 }
144172 } ,
145173
@@ -157,12 +185,11 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
157185 ..
158186 } ,
159187 ) ) => {
160- self . unsafe_ops
161- . push ( ( "access of a mutable static occurs here" , expr. span ) ) ;
188+ self . insert_span ( expr. span , "access of a mutable static occurs here" ) ;
162189 } ,
163190
164191 ExprKind :: Unary ( UnOp :: Deref , e) if self . typeck_results . expr_ty ( e) . is_raw_ptr ( ) => {
165- self . unsafe_ops . push ( ( "raw pointer dereference occurs here" , expr . span ) ) ;
192+ self . insert_span ( expr . span , "raw pointer dereference occurs here" ) ;
166193 } ,
167194
168195 ExprKind :: Call ( path_expr, _) => {
@@ -172,7 +199,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
172199 _ => None ,
173200 } ;
174201 if opt_sig. is_some_and ( |sig| sig. safety ( ) . is_unsafe ( ) ) {
175- self . unsafe_ops . push ( ( "unsafe function call occurs here" , expr . span ) ) ;
202+ self . insert_span ( expr . span , "unsafe function call occurs here" ) ;
176203 }
177204 } ,
178205
@@ -182,7 +209,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
182209 . type_dependent_def_id ( expr. hir_id )
183210 . map ( |def_id| self . tcx . fn_sig ( def_id) ) ;
184211 if opt_sig. is_some_and ( |sig| sig. skip_binder ( ) . safety ( ) . is_unsafe ( ) ) {
185- self . unsafe_ops . push ( ( "unsafe method call occurs here" , expr . span ) ) ;
212+ self . insert_span ( expr . span , "unsafe method call occurs here" ) ;
186213 }
187214 } ,
188215
@@ -203,8 +230,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeExprCollector<'tcx> {
203230 }
204231 ) )
205232 ) {
206- self . unsafe_ops
207- . push ( ( "modification of a mutable static occurs here" , expr. span ) ) ;
233+ self . insert_span ( expr. span , "modification of a mutable static occurs here" ) ;
208234 return self . visit_expr ( rhs) ;
209235 }
210236 } ,
0 commit comments