mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-15 09:27:25 +00:00
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.
This commit is contained in:
commit
06b1695814
4 changed files with 105 additions and 44 deletions
|
@ -3,6 +3,7 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||||
use clippy_utils::source::snippet_opt;
|
use clippy_utils::source::snippet_opt;
|
||||||
use clippy_utils::ty::expr_sig;
|
use clippy_utils::ty::expr_sig;
|
||||||
|
use clippy_utils::visitors::contains_unsafe_block;
|
||||||
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
|
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_errors::{Applicability, MultiSpan};
|
use rustc_errors::{Applicability, MultiSpan};
|
||||||
|
@ -10,9 +11,9 @@ use rustc_hir::def_id::DefId;
|
||||||
use rustc_hir::hir_id::HirIdMap;
|
use rustc_hir::hir_id::HirIdMap;
|
||||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg,
|
self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg,
|
||||||
ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
|
ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
|
||||||
TraitItem, TraitItemKind, TyKind,
|
TraitItem, TraitItemKind, TyKind, Unsafety,
|
||||||
};
|
};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::hir::nested_filter;
|
use rustc_middle::hir::nested_filter;
|
||||||
|
@ -88,19 +89,26 @@ declare_clippy_lint! {
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
/// ### What it does
|
/// ### What it does
|
||||||
/// This lint checks for functions that take immutable
|
/// This lint checks for functions that take immutable references and return
|
||||||
/// references and return mutable ones.
|
/// mutable ones. This will not trigger if no unsafe code exists as there
|
||||||
|
/// are multiple safe functions which will do this transformation
|
||||||
|
///
|
||||||
|
/// To be on the conservative side, if there's at least one mutable
|
||||||
|
/// reference with the output lifetime, this lint will not trigger.
|
||||||
///
|
///
|
||||||
/// ### Why is this bad?
|
/// ### Why is this bad?
|
||||||
/// This is trivially unsound, as one can create two
|
/// Creating a mutable reference which can be repeatably derived from an
|
||||||
/// mutable references from the same (immutable!) source.
|
/// immutable reference is unsound as it allows creating multiple live
|
||||||
/// This [error](https://github.com/rust-lang/rust/issues/39465)
|
/// mutable references to the same object.
|
||||||
/// actually lead to an interim Rust release 1.15.1.
|
///
|
||||||
|
/// This [error](https://github.com/rust-lang/rust/issues/39465) actually
|
||||||
|
/// lead to an interim Rust release 1.15.1.
|
||||||
///
|
///
|
||||||
/// ### Known problems
|
/// ### Known problems
|
||||||
/// To be on the conservative side, if there's at least one
|
/// This pattern is used by memory allocators to allow allocating multiple
|
||||||
/// mutable reference with the output lifetime, this lint will not trigger.
|
/// objects while returning mutable references to each one. So long as
|
||||||
/// In practice, this case is unlikely anyway.
|
/// different mutable references are returned each time such a function may
|
||||||
|
/// be safe.
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
|
@ -145,7 +153,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
check_mut_from_ref(cx, sig.decl);
|
check_mut_from_ref(cx, sig, None);
|
||||||
for arg in check_fn_args(
|
for arg in check_fn_args(
|
||||||
cx,
|
cx,
|
||||||
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
|
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
|
||||||
|
@ -170,10 +178,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||||
let hir = cx.tcx.hir();
|
let hir = cx.tcx.hir();
|
||||||
let mut parents = hir.parent_iter(body.value.hir_id);
|
let mut parents = hir.parent_iter(body.value.hir_id);
|
||||||
let (item_id, decl, is_trait_item) = match parents.next() {
|
let (item_id, sig, is_trait_item) = match parents.next() {
|
||||||
Some((_, Node::Item(i))) => {
|
Some((_, Node::Item(i))) => {
|
||||||
if let ItemKind::Fn(sig, ..) = &i.kind {
|
if let ItemKind::Fn(sig, ..) = &i.kind {
|
||||||
(i.def_id, sig.decl, false)
|
(i.def_id, sig, false)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -185,14 +193,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let ImplItemKind::Fn(sig, _) = &i.kind {
|
if let ImplItemKind::Fn(sig, _) = &i.kind {
|
||||||
(i.def_id, sig.decl, false)
|
(i.def_id, sig, false)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some((_, Node::TraitItem(i))) => {
|
Some((_, Node::TraitItem(i))) => {
|
||||||
if let TraitItemKind::Fn(sig, _) = &i.kind {
|
if let TraitItemKind::Fn(sig, _) = &i.kind {
|
||||||
(i.def_id, sig.decl, true)
|
(i.def_id, sig, true)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +208,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
check_mut_from_ref(cx, decl);
|
check_mut_from_ref(cx, sig, Some(body));
|
||||||
|
let decl = sig.decl;
|
||||||
let sig = cx.tcx.fn_sig(item_id).skip_binder();
|
let sig = cx.tcx.fn_sig(item_id).skip_binder();
|
||||||
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
|
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
|
||||||
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
|
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
|
||||||
|
@ -473,31 +482,31 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
|
fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
|
||||||
if let FnRetTy::Return(ty) = decl.output {
|
if let FnRetTy::Return(ty) = sig.decl.output
|
||||||
if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
|
&& let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
|
||||||
let mut immutables = vec![];
|
{
|
||||||
for (_, mutbl, argspan) in decl
|
let args: Option<Vec<_>> = sig
|
||||||
.inputs
|
.decl
|
||||||
.iter()
|
.inputs
|
||||||
.filter_map(get_rptr_lm)
|
.iter()
|
||||||
.filter(|&(lt, _, _)| lt.name == out.name)
|
.filter_map(get_rptr_lm)
|
||||||
{
|
.filter(|&(lt, _, _)| lt.name == out.name)
|
||||||
if mutbl == Mutability::Mut {
|
.map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
|
||||||
return;
|
.collect();
|
||||||
}
|
if let Some(args) = args
|
||||||
immutables.push(argspan);
|
&& !args.is_empty()
|
||||||
}
|
&& body.map_or(true, |body| {
|
||||||
if immutables.is_empty() {
|
sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
|
||||||
return;
|
})
|
||||||
}
|
{
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
cx,
|
cx,
|
||||||
MUT_FROM_REF,
|
MUT_FROM_REF,
|
||||||
ty.span,
|
ty.span,
|
||||||
"mutable borrow from immutable input(s)",
|
"mutable borrow from immutable input(s)",
|
||||||
|diag| {
|
|diag| {
|
||||||
let ms = MultiSpan::from_spans(immutables);
|
let ms = MultiSpan::from_spans(args);
|
||||||
diag.span_note(ms, "immutable borrow here");
|
diag.span_note(ms, "immutable borrow here");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,7 +3,8 @@ use rustc_hir as hir;
|
||||||
use rustc_hir::def::{DefKind, Res};
|
use rustc_hir::def::{DefKind, Res};
|
||||||
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
|
use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, Unsafety,
|
Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Stmt, UnOp, UnsafeSource,
|
||||||
|
Unsafety,
|
||||||
};
|
};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_middle::hir::map::Map;
|
use rustc_middle::hir::map::Map;
|
||||||
|
@ -370,3 +371,34 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
|
||||||
v.visit_expr(e);
|
v.visit_expr(e);
|
||||||
v.is_unsafe
|
v.is_unsafe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the given expression contains an unsafe block
|
||||||
|
pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
|
||||||
|
struct V<'cx, 'tcx> {
|
||||||
|
cx: &'cx LateContext<'tcx>,
|
||||||
|
found_unsafe: bool,
|
||||||
|
}
|
||||||
|
impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
|
||||||
|
type NestedFilter = nested_filter::OnlyBodies;
|
||||||
|
fn nested_visit_map(&mut self) -> Self::Map {
|
||||||
|
self.cx.tcx.hir()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_block(&mut self, b: &'tcx Block<'_>) {
|
||||||
|
if self.found_unsafe {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
|
||||||
|
self.found_unsafe = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
walk_block(self, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut v = V {
|
||||||
|
cx,
|
||||||
|
found_unsafe: false,
|
||||||
|
};
|
||||||
|
v.visit_expr(e);
|
||||||
|
v.found_unsafe
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ struct Foo;
|
||||||
|
|
||||||
impl Foo {
|
impl Foo {
|
||||||
fn this_wont_hurt_a_bit(&self) -> &mut Foo {
|
fn this_wont_hurt_a_bit(&self) -> &mut Foo {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,29 +15,37 @@ trait Ouch {
|
||||||
|
|
||||||
impl Ouch for Foo {
|
impl Ouch for Foo {
|
||||||
fn ouch(x: &Foo) -> &mut Foo {
|
fn ouch(x: &Foo) -> &mut Foo {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fail(x: &u32) -> &mut u16 {
|
fn fail(x: &u32) -> &mut u16 {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 {
|
fn fail_lifetime<'a>(x: &'a u32, y: &mut u32) -> &'a mut u32 {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
|
fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is OK, because the result borrows y
|
// this is OK, because the result borrows y
|
||||||
fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 {
|
fn works<'a>(x: &u32, y: &'a mut u32) -> &'a mut u32 {
|
||||||
unimplemented!()
|
unsafe { unimplemented!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is also OK, because the result could borrow y
|
// this is also OK, because the result could borrow y
|
||||||
fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 {
|
fn also_works<'a>(x: &'a u32, y: &'a mut u32) -> &'a mut u32 {
|
||||||
|
unsafe { unimplemented!() }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn also_broken(x: &u32) -> &mut u32 {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn without_unsafe(x: &u32) -> &mut u32 {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,5 +59,17 @@ note: immutable borrow here
|
||||||
LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
|
LL | fn fail_double<'a, 'b>(x: &'a u32, y: &'a u32, z: &'b mut u32) -> &'a mut u32 {
|
||||||
| ^^^^^^^ ^^^^^^^
|
| ^^^^^^^ ^^^^^^^
|
||||||
|
|
||||||
error: aborting due to 5 previous errors
|
error: mutable borrow from immutable input(s)
|
||||||
|
--> $DIR/mut_from_ref.rs:44:35
|
||||||
|
|
|
||||||
|
LL | unsafe fn also_broken(x: &u32) -> &mut u32 {
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
note: immutable borrow here
|
||||||
|
--> $DIR/mut_from_ref.rs:44:26
|
||||||
|
|
|
||||||
|
LL | unsafe fn also_broken(x: &u32) -> &mut u32 {
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: aborting due to 6 previous errors
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue