blob: 9d6809ea837cf40cce76b7e6b64b8466901bbca4 [file]
use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
use crate::diagnostics;
pub(super) struct CheckMutRestriction;
impl<'tcx> crate::MirLint<'tcx> for CheckMutRestriction {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
if body.tainted_by_errors.is_some() {
return;
}
let mut checker = MutRestrictionChecker { body, tcx, mutating_span: body.span };
checker.visit_body(body);
}
}
struct MutRestrictionChecker<'a, 'tcx> {
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
mutating_span: Span,
}
impl<'tcx> Visitor<'tcx> for MutRestrictionChecker<'_, 'tcx> {
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
self.mutating_span = terminator.source_info.span;
self.super_terminator(terminator, location);
}
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
self.mutating_span = statement.source_info.span;
self.super_statement(statement, location);
}
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
if context.is_mutating_use() {
let body_did = self.body.source.instance.def_id();
for (place_base, elem) in place.iter_projections() {
// Even when the field is an array or slice and is accessed by index,
// as in `foo.array[0]`, the projection chain still contains a field
// projection. Therefore, it is sufficient to check for field projections.
let ProjectionElem::Field(field_idx, _field_ty) = elem else {
continue;
};
let base_ty = place_base.ty(self.body, self.tcx);
// Field projections are also used for tuples, closures, and coroutines,
// but mutability restrictions only apply to ADT fields.
// Mutating an ADT field through a captured value still produces a
// separate field projection whose base type is that ADT.
// Therefore, it is sufficient to check for ADT base types.
// Generic arguments do not affect the field's restriction, so we ignore them.
let ty::Adt(adt_def, _args) = *base_ty.ty.kind() else {
continue;
};
let variant_def = if let Some(idx) = base_ty.variant_index {
assert!(adt_def.is_enum());
adt_def.variant(idx)
} else {
adt_def.non_enum_variant()
};
let field_def: &ty::FieldDef = &variant_def.fields[field_idx];
let mut_restriction = field_def.mut_restriction;
if !mut_restriction.is_allowed_in(body_did, self.tcx) {
self.tcx.dcx().emit_err(diagnostics::MutOfRestrictedField {
mut_span: self.mutating_span,
restriction_span: mut_restriction.expect_span(),
name: field_def.name,
restriction_path: mut_restriction.restriction_path(self.tcx),
});
}
}
}
self.super_place(place, context, location);
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
if let Rvalue::Aggregate(aggr, _) = rvalue
&& let AggregateKind::Adt(adt_did, variant_idx, _args, _user_ty, active_field) = &**aggr
{
let body_did = self.body.source.instance.def_id();
let adt = self.tcx.adt_def(*adt_did);
let variant = &adt.variants()[*variant_idx];
if let Some(field_idx) = active_field {
// union
let field_def = &variant.fields[*field_idx];
let mut_restriction = field_def.mut_restriction;
if !mut_restriction.is_allowed_in(body_did, self.tcx) {
self.tcx.dcx().emit_err(diagnostics::ConstructionOfTyWithMutRestrictedField {
construction_span: self.mutating_span,
restriction_span: mut_restriction.expect_span(),
name: variant.name,
descr: adt.variant_descr(),
restriction_path: mut_restriction.restriction_path(self.tcx),
});
}
} else {
// struct / enum variant
let mut_restriction =
variant.fields.iter().fold(ty::RestrictionKind::Unrestricted, |acc, field| {
acc.stricter_of(field.mut_restriction, self.tcx)
});
if !mut_restriction.is_allowed_in(body_did, self.tcx) {
self.tcx.dcx().emit_err(diagnostics::ConstructionOfTyWithMutRestrictedField {
construction_span: self.mutating_span,
restriction_span: mut_restriction.expect_span(),
name: variant.name,
descr: adt.variant_descr(),
restriction_path: mut_restriction.restriction_path(self.tcx),
});
}
}
}
self.super_rvalue(rvalue, location);
}
}