3
3
use clippy_utils:: diagnostics:: { span_lint, span_lint_and_sugg, span_lint_and_then} ;
4
4
use clippy_utils:: source:: snippet_opt;
5
5
use clippy_utils:: ty:: expr_sig;
6
+ use clippy_utils:: visitors:: contains_unsafe_block;
6
7
use clippy_utils:: { get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths} ;
7
8
use if_chain:: if_chain;
8
9
use rustc_errors:: { Applicability , MultiSpan } ;
9
10
use rustc_hir:: def_id:: DefId ;
10
11
use rustc_hir:: hir_id:: HirIdMap ;
11
12
use rustc_hir:: intravisit:: { walk_expr, Visitor } ;
12
13
use rustc_hir:: {
13
- self as hir, AnonConst , BinOpKind , BindingAnnotation , Body , Expr , ExprKind , FnDecl , FnRetTy , GenericArg ,
14
+ self as hir, AnonConst , BinOpKind , BindingAnnotation , Body , Expr , ExprKind , FnRetTy , FnSig , GenericArg ,
14
15
ImplItemKind , ItemKind , Lifetime , LifetimeName , Mutability , Node , Param , ParamName , PatKind , QPath , TraitFn ,
15
- TraitItem , TraitItemKind , TyKind ,
16
+ TraitItem , TraitItemKind , TyKind , Unsafety ,
16
17
} ;
17
18
use rustc_lint:: { LateContext , LateLintPass } ;
18
19
use rustc_middle:: hir:: nested_filter;
@@ -88,19 +89,26 @@ declare_clippy_lint! {
88
89
89
90
declare_clippy_lint ! {
90
91
/// ### What it does
91
- /// This lint checks for functions that take immutable
92
- /// references and return mutable ones.
92
+ /// This lint checks for functions that take immutable references and return
93
+ /// mutable ones. This will not trigger if no unsafe code exists as there
94
+ /// are multiple safe functions which will do this transformation
95
+ ///
96
+ /// To be on the conservative side, if there's at least one mutable
97
+ /// reference with the output lifetime, this lint will not trigger.
93
98
///
94
99
/// ### Why is this bad?
95
- /// This is trivially unsound, as one can create two
96
- /// mutable references from the same (immutable!) source.
97
- /// This [error](https://github.com/rust-lang/rust/issues/39465)
98
- /// actually lead to an interim Rust release 1.15.1.
100
+ /// Creating a mutable reference which can be repeatably derived from an
101
+ /// immutable reference is unsound as it allows creating multiple live
102
+ /// mutable references to the same object.
103
+ ///
104
+ /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
105
+ /// lead to an interim Rust release 1.15.1.
99
106
///
100
107
/// ### Known problems
101
- /// To be on the conservative side, if there's at least one
102
- /// mutable reference with the output lifetime, this lint will not trigger.
103
- /// In practice, this case is unlikely anyway.
108
+ /// This pattern is used by memory allocators to allow allocating multiple
109
+ /// objects while returning mutable references to each one. So long as
110
+ /// different mutable references are returned each time such a function may
111
+ /// be safe.
104
112
///
105
113
/// ### Example
106
114
/// ```ignore
@@ -145,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
145
153
return ;
146
154
}
147
155
148
- check_mut_from_ref ( cx, sig. decl ) ;
156
+ check_mut_from_ref ( cx, sig, None ) ;
149
157
for arg in check_fn_args (
150
158
cx,
151
159
cx. tcx . fn_sig ( item. def_id ) . skip_binder ( ) . inputs ( ) ,
@@ -170,10 +178,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
170
178
fn check_body ( & mut self , cx : & LateContext < ' tcx > , body : & ' tcx Body < ' _ > ) {
171
179
let hir = cx. tcx . hir ( ) ;
172
180
let mut parents = hir. parent_iter ( body. value . hir_id ) ;
173
- let ( item_id, decl , is_trait_item) = match parents. next ( ) {
181
+ let ( item_id, sig , is_trait_item) = match parents. next ( ) {
174
182
Some ( ( _, Node :: Item ( i) ) ) => {
175
183
if let ItemKind :: Fn ( sig, ..) = & i. kind {
176
- ( i. def_id , sig. decl , false )
184
+ ( i. def_id , sig, false )
177
185
} else {
178
186
return ;
179
187
}
@@ -185,22 +193,23 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
185
193
return ;
186
194
}
187
195
if let ImplItemKind :: Fn ( sig, _) = & i. kind {
188
- ( i. def_id , sig. decl , false )
196
+ ( i. def_id , sig, false )
189
197
} else {
190
198
return ;
191
199
}
192
200
} ,
193
201
Some ( ( _, Node :: TraitItem ( i) ) ) => {
194
202
if let TraitItemKind :: Fn ( sig, _) = & i. kind {
195
- ( i. def_id , sig. decl , true )
203
+ ( i. def_id , sig, true )
196
204
} else {
197
205
return ;
198
206
}
199
207
} ,
200
208
_ => return ,
201
209
} ;
202
210
203
- check_mut_from_ref ( cx, decl) ;
211
+ check_mut_from_ref ( cx, sig, Some ( body) ) ;
212
+ let decl = sig. decl ;
204
213
let sig = cx. tcx . fn_sig ( item_id) . skip_binder ( ) ;
205
214
let lint_args: Vec < _ > = check_fn_args ( cx, sig. inputs ( ) , decl. inputs , body. params )
206
215
. filter ( |arg| !is_trait_item || arg. mutability ( ) == Mutability :: Not )
@@ -473,31 +482,31 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
473
482
} )
474
483
}
475
484
476
- fn check_mut_from_ref ( cx : & LateContext < ' _ > , decl : & FnDecl < ' _ > ) {
477
- if let FnRetTy :: Return ( ty) = decl. output {
478
- if let Some ( ( out, Mutability :: Mut , _) ) = get_rptr_lm ( ty) {
479
- let mut immutables = vec ! [ ] ;
480
- for ( _ , mutbl , argspan ) in decl
481
- . inputs
482
- . iter ( )
483
- . filter_map ( get_rptr_lm )
484
- . filter ( | & ( lt , _ , _ ) | lt . name == out . name )
485
- {
486
- if mutbl == Mutability :: Mut {
487
- return ;
488
- }
489
- immutables . push ( argspan ) ;
490
- }
491
- if immutables . is_empty ( ) {
492
- return ;
493
- }
485
+ fn check_mut_from_ref < ' tcx > ( cx : & LateContext < ' tcx > , sig : & FnSig < ' _ > , body : Option < & ' tcx Body < ' _ > > ) {
486
+ if let FnRetTy :: Return ( ty) = sig . decl . output
487
+ && let Some ( ( out, Mutability :: Mut , _) ) = get_rptr_lm ( ty)
488
+ {
489
+ let args : Option < Vec < _ > > = sig
490
+ . decl
491
+ . inputs
492
+ . iter ( )
493
+ . filter_map ( get_rptr_lm )
494
+ . filter ( | & ( lt , _ , _ ) | lt . name == out . name )
495
+ . map ( | ( _ , mutability , span ) | ( mutability == Mutability :: Not ) . then ( || span ) )
496
+ . collect ( ) ;
497
+ if let Some ( args ) = args
498
+ && !args . is_empty ( )
499
+ && body . map_or ( true , |body| {
500
+ sig . header . unsafety == Unsafety :: Unsafe || contains_unsafe_block ( cx , & body . value )
501
+ } )
502
+ {
494
503
span_lint_and_then (
495
504
cx,
496
505
MUT_FROM_REF ,
497
506
ty. span ,
498
507
"mutable borrow from immutable input(s)" ,
499
508
|diag| {
500
- let ms = MultiSpan :: from_spans ( immutables ) ;
509
+ let ms = MultiSpan :: from_spans ( args ) ;
501
510
diag. span_note ( ms, "immutable borrow here" ) ;
502
511
} ,
503
512
) ;
0 commit comments