Skip to main content

rustc_mir_transform/
check_mut_restriction.rs

1use rustc_middle::mir::visit::{PlaceContext, Visitor};
2use rustc_middle::mir::*;
3use rustc_middle::ty::{self, TyCtxt};
4use rustc_span::Span;
5
6use crate::diagnostics;
7
8pub(super) struct CheckMutRestriction;
9
10impl<'tcx> crate::MirLint<'tcx> for CheckMutRestriction {
11    fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
12        if body.tainted_by_errors.is_some() {
13            return;
14        }
15        let mut checker = MutRestrictionChecker { body, tcx, mutating_span: body.span };
16        checker.visit_body(body);
17    }
18}
19
20struct MutRestrictionChecker<'a, 'tcx> {
21    body: &'a Body<'tcx>,
22    tcx: TyCtxt<'tcx>,
23    mutating_span: Span,
24}
25
26impl<'tcx> Visitor<'tcx> for MutRestrictionChecker<'_, 'tcx> {
27    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
28        self.mutating_span = terminator.source_info.span;
29        self.super_terminator(terminator, location);
30    }
31
32    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
33        self.mutating_span = statement.source_info.span;
34        self.super_statement(statement, location);
35    }
36
37    fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
38        if context.is_mutating_use() {
39            let body_did = self.body.source.instance.def_id();
40
41            for (place_base, elem) in place.iter_projections() {
42                // Even when the field is an array or slice and is accessed by index,
43                // as in `foo.array[0]`, the projection chain still contains a field
44                // projection. Therefore, it is sufficient to check for field projections.
45                let ProjectionElem::Field(field_idx, _field_ty) = elem else {
46                    continue;
47                };
48
49                let base_ty = place_base.ty(self.body, self.tcx);
50
51                // Field projections are also used for tuples, closures, and coroutines,
52                // but mutability restrictions only apply to ADT fields.
53                // Mutating an ADT field through a captured value still produces a
54                // separate field projection whose base type is that ADT.
55                // Therefore, it is sufficient to check for ADT base types.
56                // Generic arguments do not affect the field's restriction, so we ignore them.
57                let ty::Adt(adt_def, _args) = *base_ty.ty.kind() else {
58                    continue;
59                };
60
61                let variant_def = if let Some(idx) = base_ty.variant_index {
62                    assert!(adt_def.is_enum());
63                    adt_def.variant(idx)
64                } else {
65                    adt_def.non_enum_variant()
66                };
67
68                let field_def: &ty::FieldDef = &variant_def.fields[field_idx];
69                let mut_restriction = field_def.mut_restriction;
70
71                if !mut_restriction.is_allowed_in(body_did, self.tcx) {
72                    self.tcx.dcx().emit_err(diagnostics::MutOfRestrictedField {
73                        mut_span: self.mutating_span,
74                        restriction_span: mut_restriction.expect_span(),
75                        name: field_def.name,
76                        restriction_path: mut_restriction.restriction_path(self.tcx),
77                    });
78                }
79            }
80        }
81
82        self.super_place(place, context, location);
83    }
84
85    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
86        if let Rvalue::Aggregate(aggr, _) = rvalue
87            && let AggregateKind::Adt(adt_did, variant_idx, _args, _user_ty, active_field) = &**aggr
88        {
89            let body_did = self.body.source.instance.def_id();
90            let adt = self.tcx.adt_def(*adt_did);
91            let variant = &adt.variants()[*variant_idx];
92
93            if let Some(field_idx) = active_field {
94                // union
95                let field_def = &variant.fields[*field_idx];
96                let mut_restriction = field_def.mut_restriction;
97                if !mut_restriction.is_allowed_in(body_did, self.tcx) {
98                    self.tcx.dcx().emit_err(diagnostics::ConstructionOfTyWithMutRestrictedField {
99                        construction_span: self.mutating_span,
100                        restriction_span: mut_restriction.expect_span(),
101                        name: variant.name,
102                        descr: adt.variant_descr(),
103                        restriction_path: mut_restriction.restriction_path(self.tcx),
104                    });
105                }
106            } else {
107                // struct / enum variant
108                let mut_restriction =
109                    variant.fields.iter().fold(ty::RestrictionKind::Unrestricted, |acc, field| {
110                        acc.stricter_of(field.mut_restriction, self.tcx)
111                    });
112                if !mut_restriction.is_allowed_in(body_did, self.tcx) {
113                    self.tcx.dcx().emit_err(diagnostics::ConstructionOfTyWithMutRestrictedField {
114                        construction_span: self.mutating_span,
115                        restriction_span: mut_restriction.expect_span(),
116                        name: variant.name,
117                        descr: adt.variant_descr(),
118                        restriction_path: mut_restriction.restriction_path(self.tcx),
119                    });
120                }
121            }
122        }
123
124        self.super_rvalue(rvalue, location);
125    }
126}