Skip to main content

rustc_mir_transform/
check_mut_restriction.rs

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