Auto merge of #75573 - Aaron1011:feature/const-mutation-lint, r=oli-obk
Add CONST_ITEM_MUTATION lint Fixes #74053 Fixes #55721 This PR adds a new lint `CONST_ITEM_MUTATION`. Given an item `const FOO: SomeType = ..`, this lint fires on: * Attempting to write directly to a field (`FOO.field = some_val`) or array entry (`FOO.array_field[0] = val`) * Taking a mutable reference to the `const` item (`&mut FOO`), including through an autoderef `FOO.some_mut_self_method()` The lint message explains that since each use of a constant creates a new temporary, the original `const` item will not be modified.
This commit is contained in:
commit
88197214b8
22 changed files with 427 additions and 116 deletions
|
@ -804,68 +804,51 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
|||
debug!("move_spans: target_temp = {:?}", target_temp);
|
||||
|
||||
if let Some(Terminator {
|
||||
kind: TerminatorKind::Call { func, args, fn_span, from_hir_call, .. },
|
||||
..
|
||||
kind: TerminatorKind::Call { fn_span, from_hir_call, .. }, ..
|
||||
}) = &self.body[location.block].terminator
|
||||
{
|
||||
let mut method_did = None;
|
||||
if let Operand::Constant(box Constant { literal: ty::Const { ty, .. }, .. }) = func {
|
||||
if let ty::FnDef(def_id, _) = *ty.kind() {
|
||||
debug!("move_spans: fn = {:?}", def_id);
|
||||
if let Some(ty::AssocItem { fn_has_self_parameter, .. }) =
|
||||
self.infcx.tcx.opt_associated_item(def_id)
|
||||
{
|
||||
if *fn_has_self_parameter {
|
||||
method_did = Some(def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let method_did = if let Some(method_did) =
|
||||
crate::util::find_self_call(self.infcx.tcx, &self.body, target_temp, location.block)
|
||||
{
|
||||
method_did
|
||||
} else {
|
||||
return normal_ret;
|
||||
};
|
||||
|
||||
let tcx = self.infcx.tcx;
|
||||
let method_did = if let Some(did) = method_did { did } else { return normal_ret };
|
||||
|
||||
if let [Operand::Move(self_place), ..] = **args {
|
||||
if self_place.as_local() == Some(target_temp) {
|
||||
let parent = tcx.parent(method_did);
|
||||
let is_fn_once = parent == tcx.lang_items().fn_once_trait();
|
||||
let is_operator = !from_hir_call
|
||||
&& parent.map_or(false, |p| {
|
||||
tcx.lang_items().group(LangItemGroup::Op).contains(&p)
|
||||
});
|
||||
let fn_call_span = *fn_span;
|
||||
let parent = tcx.parent(method_did);
|
||||
let is_fn_once = parent == tcx.lang_items().fn_once_trait();
|
||||
let is_operator = !from_hir_call
|
||||
&& parent.map_or(false, |p| tcx.lang_items().group(LangItemGroup::Op).contains(&p));
|
||||
let fn_call_span = *fn_span;
|
||||
|
||||
let self_arg = tcx.fn_arg_names(method_did)[0];
|
||||
let self_arg = tcx.fn_arg_names(method_did)[0];
|
||||
|
||||
let kind = if is_fn_once {
|
||||
FnSelfUseKind::FnOnceCall
|
||||
} else if is_operator {
|
||||
FnSelfUseKind::Operator { self_arg }
|
||||
} else {
|
||||
debug!(
|
||||
"move_spans: method_did={:?}, fn_call_span={:?}",
|
||||
method_did, fn_call_span
|
||||
);
|
||||
let implicit_into_iter = matches!(
|
||||
fn_call_span.desugaring_kind(),
|
||||
Some(DesugaringKind::ForLoop(ForLoopLoc::IntoIter))
|
||||
);
|
||||
FnSelfUseKind::Normal { self_arg, implicit_into_iter }
|
||||
};
|
||||
let kind = if is_fn_once {
|
||||
FnSelfUseKind::FnOnceCall
|
||||
} else if is_operator {
|
||||
FnSelfUseKind::Operator { self_arg }
|
||||
} else {
|
||||
debug!("move_spans: method_did={:?}, fn_call_span={:?}", method_did, fn_call_span);
|
||||
let implicit_into_iter = matches!(
|
||||
fn_call_span.desugaring_kind(),
|
||||
Some(DesugaringKind::ForLoop(ForLoopLoc::IntoIter))
|
||||
);
|
||||
FnSelfUseKind::Normal { self_arg, implicit_into_iter }
|
||||
};
|
||||
|
||||
return FnSelfUse {
|
||||
var_span: stmt.source_info.span,
|
||||
fn_call_span,
|
||||
fn_span: self
|
||||
.infcx
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.guess_head_span(self.infcx.tcx.def_span(method_did)),
|
||||
kind,
|
||||
};
|
||||
}
|
||||
}
|
||||
return FnSelfUse {
|
||||
var_span: stmt.source_info.span,
|
||||
fn_call_span,
|
||||
fn_span: self
|
||||
.infcx
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.guess_head_span(self.infcx.tcx.def_span(method_did)),
|
||||
kind,
|
||||
};
|
||||
}
|
||||
normal_ret
|
||||
}
|
||||
|
|
114
compiler/rustc_mir/src/transform/check_const_item_mutation.rs
Normal file
114
compiler/rustc_mir/src/transform/check_const_item_mutation.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
use rustc_errors::DiagnosticBuilder;
|
||||
use rustc_middle::lint::LintDiagnosticBuilder;
|
||||
use rustc_middle::mir::visit::Visitor;
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::lint::builtin::CONST_ITEM_MUTATION;
|
||||
use rustc_span::def_id::DefId;
|
||||
|
||||
use crate::transform::{MirPass, MirSource};
|
||||
|
||||
pub struct CheckConstItemMutation;
|
||||
|
||||
impl<'tcx> MirPass<'tcx> for CheckConstItemMutation {
|
||||
fn run_pass(&self, tcx: TyCtxt<'tcx>, _src: MirSource<'tcx>, body: &mut Body<'tcx>) {
|
||||
let mut checker = ConstMutationChecker { body, tcx, target_local: None };
|
||||
checker.visit_body(&body);
|
||||
}
|
||||
}
|
||||
|
||||
struct ConstMutationChecker<'a, 'tcx> {
|
||||
body: &'a Body<'tcx>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
target_local: Option<Local>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ConstMutationChecker<'a, 'tcx> {
|
||||
fn is_const_item(&self, local: Local) -> Option<DefId> {
|
||||
if let Some(box LocalInfo::ConstRef { def_id }) = self.body.local_decls[local].local_info {
|
||||
Some(def_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn lint_const_item_usage(
|
||||
&self,
|
||||
const_item: DefId,
|
||||
location: Location,
|
||||
decorate: impl for<'b> FnOnce(LintDiagnosticBuilder<'b>) -> DiagnosticBuilder<'b>,
|
||||
) {
|
||||
let source_info = self.body.source_info(location);
|
||||
let lint_root = self.body.source_scopes[source_info.scope]
|
||||
.local_data
|
||||
.as_ref()
|
||||
.assert_crate_local()
|
||||
.lint_root;
|
||||
|
||||
self.tcx.struct_span_lint_hir(CONST_ITEM_MUTATION, lint_root, source_info.span, |lint| {
|
||||
decorate(lint)
|
||||
.span_note(self.tcx.def_span(const_item), "`const` item defined here")
|
||||
.emit()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for ConstMutationChecker<'a, 'tcx> {
|
||||
fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) {
|
||||
if let StatementKind::Assign(box (lhs, _)) = &stmt.kind {
|
||||
// Check for assignment to fields of a constant
|
||||
// Assigning directly to a constant (e.g. `FOO = true;`) is a hard error,
|
||||
// so emitting a lint would be redundant.
|
||||
if !lhs.projection.is_empty() {
|
||||
if let Some(def_id) = self.is_const_item(lhs.local) {
|
||||
self.lint_const_item_usage(def_id, loc, |lint| {
|
||||
let mut lint = lint.build("attempting to modify a `const` item");
|
||||
lint.note("each usage of a `const` item creates a new temporary - the original `const` item will not be modified");
|
||||
lint
|
||||
})
|
||||
}
|
||||
}
|
||||
// We are looking for MIR of the form:
|
||||
//
|
||||
// ```
|
||||
// _1 = const FOO;
|
||||
// _2 = &mut _1;
|
||||
// method_call(_2, ..)
|
||||
// ```
|
||||
//
|
||||
// Record our current LHS, so that we can detect this
|
||||
// pattern in `visit_rvalue`
|
||||
self.target_local = lhs.as_local();
|
||||
}
|
||||
self.super_statement(stmt, loc);
|
||||
self.target_local = None;
|
||||
}
|
||||
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, loc: Location) {
|
||||
if let Rvalue::Ref(_, BorrowKind::Mut { .. }, place) = rvalue {
|
||||
let local = place.local;
|
||||
if let Some(def_id) = self.is_const_item(local) {
|
||||
// If this Rvalue is being used as the right-hand side of a
|
||||
// `StatementKind::Assign`, see if it ends up getting used as
|
||||
// the `self` parameter of a method call (as the terminator of our current
|
||||
// BasicBlock). If so, we emit a more specific lint.
|
||||
let method_did = self.target_local.and_then(|target_local| {
|
||||
crate::util::find_self_call(self.tcx, &self.body, target_local, loc.block)
|
||||
});
|
||||
let lint_loc =
|
||||
if method_did.is_some() { self.body.terminator_loc(loc.block) } else { loc };
|
||||
self.lint_const_item_usage(def_id, lint_loc, |lint| {
|
||||
let mut lint = lint.build("taking a mutable reference to a `const` item");
|
||||
lint
|
||||
.note("each usage of a `const` item creates a new temporary")
|
||||
.note("the mutable reference will refer to this temporary, not the original `const` item");
|
||||
|
||||
if let Some(method_did) = method_did {
|
||||
lint.span_note(self.tcx.def_span(method_did), "mutable reference created due to call to this method");
|
||||
}
|
||||
|
||||
lint
|
||||
});
|
||||
}
|
||||
}
|
||||
self.super_rvalue(rvalue, loc);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ use std::borrow::Cow;
|
|||
pub mod add_call_guards;
|
||||
pub mod add_moves_for_packed_drops;
|
||||
pub mod add_retag;
|
||||
pub mod check_const_item_mutation;
|
||||
pub mod check_consts;
|
||||
pub mod check_packed_ref;
|
||||
pub mod check_unsafety;
|
||||
|
@ -307,6 +308,7 @@ fn mir_const<'tcx>(
|
|||
&[&[
|
||||
// MIR-level lints.
|
||||
&check_packed_ref::CheckPackedRef,
|
||||
&check_const_item_mutation::CheckConstItemMutation,
|
||||
// What we need to do constant evaluation.
|
||||
&simplify::SimplifyCfg::new("initial"),
|
||||
&rustc_peek::SanityCheck,
|
||||
|
|
35
compiler/rustc_mir/src/util/find_self_call.rs
Normal file
35
compiler/rustc_mir/src/util/find_self_call.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_span::def_id::DefId;
|
||||
|
||||
/// Checks if the specified `local` is used as the `self` prameter of a method call
|
||||
/// in the provided `BasicBlock`. If it is, then the `DefId` of the called method is
|
||||
/// returned.
|
||||
pub fn find_self_call(
|
||||
tcx: TyCtxt<'_>,
|
||||
body: &Body<'_>,
|
||||
local: Local,
|
||||
block: BasicBlock,
|
||||
) -> Option<DefId> {
|
||||
debug!("find_self_call(local={:?}): terminator={:?}", local, &body[block].terminator);
|
||||
if let Some(Terminator { kind: TerminatorKind::Call { func, args, .. }, .. }) =
|
||||
&body[block].terminator
|
||||
{
|
||||
debug!("find_self_call: func={:?}", func);
|
||||
if let Operand::Constant(box Constant { literal: ty::Const { ty, .. }, .. }) = func {
|
||||
if let ty::FnDef(def_id, _) = *ty.kind() {
|
||||
if let Some(ty::AssocItem { fn_has_self_parameter: true, .. }) =
|
||||
tcx.opt_associated_item(def_id)
|
||||
{
|
||||
debug!("find_self_call: args={:?}", args);
|
||||
if let [Operand::Move(self_place) | Operand::Copy(self_place), ..] = **args {
|
||||
if self_place.as_local() == Some(local) {
|
||||
return Some(def_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
|
@ -7,12 +7,14 @@ pub mod storage;
|
|||
|
||||
mod alignment;
|
||||
pub mod collect_writes;
|
||||
mod find_self_call;
|
||||
mod graphviz;
|
||||
pub(crate) mod pretty;
|
||||
pub(crate) mod spanview;
|
||||
|
||||
pub use self::aggregate::expand_aggregate;
|
||||
pub use self::alignment::is_disaligned;
|
||||
pub use self::find_self_call::find_self_call;
|
||||
pub use self::graphviz::write_node_label as write_graphviz_node_label;
|
||||
pub use self::graphviz::{graphviz_safe_def_name, write_mir_graphviz};
|
||||
pub use self::pretty::{dump_enabled, dump_mir, write_mir_pretty, PassWhere};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue