Skip to content

Commit 06b1695

Browse files
committed
Auto merge of #8647 - Jarcho:mut_from_ref_6326, r=giraffate
Only lint `mut_from_ref` when unsafe code is used fixes #6326 changelog: Only lint `mut_from_ref` when unsafe code is used.
2 parents d8c97e6 + 1905425 commit 06b1695

File tree

4 files changed

+105
-44
lines changed

4 files changed

+105
-44
lines changed

clippy_lints/src/ptr.rs

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
44
use clippy_utils::source::snippet_opt;
55
use clippy_utils::ty::expr_sig;
6+
use clippy_utils::visitors::contains_unsafe_block;
67
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
78
use if_chain::if_chain;
89
use rustc_errors::{Applicability, MultiSpan};
910
use rustc_hir::def_id::DefId;
1011
use rustc_hir::hir_id::HirIdMap;
1112
use rustc_hir::intravisit::{walk_expr, Visitor};
1213
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,
1415
ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
15-
TraitItem, TraitItemKind, TyKind,
16+
TraitItem, TraitItemKind, TyKind, Unsafety,
1617
};
1718
use rustc_lint::{LateContext, LateLintPass};
1819
use rustc_middle::hir::nested_filter;
@@ -88,19 +89,26 @@ declare_clippy_lint! {
8889

8990
declare_clippy_lint! {
9091
/// ### 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.
9398
///
9499
/// ### 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.
99106
///
100107
/// ### 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.
104112
///
105113
/// ### Example
106114
/// ```ignore
@@ -145,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
145153
return;
146154
}
147155

148-
check_mut_from_ref(cx, sig.decl);
156+
check_mut_from_ref(cx, sig, None);
149157
for arg in check_fn_args(
150158
cx,
151159
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
@@ -170,10 +178,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
170178
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
171179
let hir = cx.tcx.hir();
172180
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() {
174182
Some((_, Node::Item(i))) => {
175183
if let ItemKind::Fn(sig, ..) = &i.kind {
176-
(i.def_id, sig.decl, false)
184+
(i.def_id, sig, false)
177185
} else {
178186
return;
179187
}
@@ -185,22 +193,23 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
185193
return;
186194
}
187195
if let ImplItemKind::Fn(sig, _) = &i.kind {
188-
(i.def_id, sig.decl, false)
196+
(i.def_id, sig, false)
189197
} else {
190198
return;
191199
}
192200
},
193201
Some((_, Node::TraitItem(i))) => {
194202
if let TraitItemKind::Fn(sig, _) = &i.kind {
195-
(i.def_id, sig.decl, true)
203+
(i.def_id, sig, true)
196204
} else {
197205
return;
198206
}
199207
},
200208
_ => return,
201209
};
202210

203-
check_mut_from_ref(cx, decl);
211+
check_mut_from_ref(cx, sig, Some(body));
212+
let decl = sig.decl;
204213
let sig = cx.tcx.fn_sig(item_id).skip_binder();
205214
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
206215
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
@@ -473,31 +482,31 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
473482
})
474483
}
475484

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+
{
494503
span_lint_and_then(
495504
cx,
496505
MUT_FROM_REF,
497506
ty.span,
498507
"mutable borrow from immutable input(s)",
499508
|diag| {
500-
let ms = MultiSpan::from_spans(immutables);
509+
let ms = MultiSpan::from_spans(args);
501510
diag.span_note(ms, "immutable borrow here");
502511
},
503512
);

clippy_utils/src/visitors.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use rustc_hir as hir;
33
use rustc_hir::def::{DefKind, Res};
44
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
55
use rustc_hir::{
6-
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, Unsafety,
6+
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource,
7+
Unsafety,
78
};
89
use rustc_lint::LateContext;
910
use rustc_middle::hir::map::Map;
@@ -370,3 +371,34 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
370371
v.visit_expr(e);
371372
v.is_unsafe
372373
}
374+
375+
/// Checks if the given expression contains an unsafe block
376+
pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
377+
struct V<'cx, 'tcx> {
378+
cx: &'cx LateContext<'tcx>,
379+
found_unsafe: bool,
380+
}
381+
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
382+
type NestedFilter = nested_filter::OnlyBodies;
383+
fn nested_visit_map(&mut self) -> Self::Map {
384+
self.cx.tcx.hir()
385+
}
386+
387+
fn visit_block(&mut self, b: &'tcx Block<'_>) {
388+
if self.found_unsafe {
389+
return;
390+
}
391+
if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
392+
self.found_unsafe = true;
393+
return;
394+
}
395+
walk_block(self, b);
396+
}
397+
}
398+
let mut v = V {
399+
cx,
400+
found_unsafe: false,
401+
};
402+
v.visit_expr(e);
403+
v.found_unsafe
404+
}

tests/ui/mut_from_ref.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ struct Foo;
55

66
impl Foo {
77
fn this_wont_hurt_a_bit(&self) -> &mut Foo {
8-
unimplemented!()
8+
unsafe { unimplemented!() }
99
}
1010
}
1111

@@ -15,29 +15,37 @@ trait Ouch {
1515

1616
impl Ouch for Foo {
1717
fn ouch(x: &Foo) -> &mut Foo {
18-
unimplemented!()
18+
unsafe { unimplemented!() }
1919
}
2020
}
2121

2222
fn fail(x: &u32) -> &mut u16 {
23-
unimplemented!()
23+
unsafe { unimplemented!() }
2424
}
2525

2626
fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 {
27-
unimplemented!()
27+
unsafe { unimplemented!() }
2828
}
2929

3030
fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
31-
unimplemented!()
31+
unsafe { unimplemented!() }
3232
}
3333

3434
// this is OK, because the result borrows y
3535
fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 {
36-
unimplemented!()
36+
unsafe { unimplemented!() }
3737
}
3838

3939
// this is also OK, because the result could borrow y
4040
fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 {
41+
unsafe { unimplemented!() }
42+
}
43+
44+
unsafe fn also_broken(x: &u32) -> &mut u32 {
45+
unimplemented!()
46+
}
47+
48+
fn without_unsafe(x: &u32) -> &mut u32 {
4149
unimplemented!()
4250
}
4351

tests/ui/mut_from_ref.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,17 @@ note: immutable borrow here
5959
LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
6060
| ^^^^^^^ ^^^^^^^
6161

62-
error: aborting due to 5 previous errors
62+
error: mutable borrow from immutable input(s)
63+
--> $DIR/mut_from_ref.rs:44:35
64+
|
65+
LL | unsafe fn also_broken(x: &u32) -> &mut u32 {
66+
| ^^^^^^^^
67+
|
68+
note: immutable borrow here
69+
--> $DIR/mut_from_ref.rs:44:26
70+
|
71+
LL | unsafe fn also_broken(x: &u32) -> &mut u32 {
72+
| ^^^^
73+
74+
error: aborting due to 6 previous errors
6375

0 commit comments

Comments
 (0)