summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/record_consumed_borrow.rs
blob: fa3887362d99caa3ec6196ba076e07bca69b82f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
use super::TrackedValue;
use crate::{
    expr_use_visitor::{self, ExprUseVisitor},
    FnCtxt,
};
use hir::{def_id::DefId, Body, HirId, HirIdMap};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_middle::ty::{ParamEnv, TyCtxt};
use rustc_middle::{
    hir::place::{PlaceBase, Projection, ProjectionKind},
    ty::TypeVisitableExt,
};

pub(super) fn find_consumed_and_borrowed<'a, 'tcx>(
    fcx: &'a FnCtxt<'a, 'tcx>,
    def_id: DefId,
    body: &'tcx Body<'tcx>,
) -> ConsumedAndBorrowedPlaces {
    let mut expr_use_visitor = ExprUseDelegate::new(fcx.tcx, fcx.param_env);
    expr_use_visitor.consume_body(fcx, def_id, body);
    expr_use_visitor.places
}

pub(super) struct ConsumedAndBorrowedPlaces {
    /// Records the variables/expressions that are dropped by a given expression.
    ///
    /// The key is the hir-id of the expression, and the value is a set or hir-ids for variables
    /// or values that are consumed by that expression.
    ///
    /// Note that this set excludes "partial drops" -- for example, a statement like `drop(x.y)` is
    /// not considered a drop of `x`, although it would be a drop of `x.y`.
    pub(super) consumed: HirIdMap<FxHashSet<TrackedValue>>,

    /// A set of hir-ids of values or variables that are borrowed at some point within the body.
    pub(super) borrowed: FxHashSet<TrackedValue>,

    /// A set of hir-ids of values or variables that are borrowed at some point within the body.
    pub(super) borrowed_temporaries: FxHashSet<HirId>,
}

/// Works with ExprUseVisitor to find interesting values for the drop range analysis.
///
/// Interesting values are those that are either dropped or borrowed. For dropped values, we also
/// record the parent expression, which is the point where the drop actually takes place.
struct ExprUseDelegate<'tcx> {
    tcx: TyCtxt<'tcx>,
    param_env: ParamEnv<'tcx>,
    places: ConsumedAndBorrowedPlaces,
}

impl<'tcx> ExprUseDelegate<'tcx> {
    fn new(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self {
        Self {
            tcx,
            param_env,
            places: ConsumedAndBorrowedPlaces {
                consumed: <_>::default(),
                borrowed: <_>::default(),
                borrowed_temporaries: <_>::default(),
            },
        }
    }

    fn consume_body(&mut self, fcx: &'_ FnCtxt<'_, 'tcx>, def_id: DefId, body: &'tcx Body<'tcx>) {
        // Run ExprUseVisitor to find where values are consumed.
        ExprUseVisitor::new(
            self,
            &fcx.infcx,
            def_id.expect_local(),
            fcx.param_env,
            &fcx.typeck_results.borrow(),
        )
        .consume_body(body);
    }

    fn mark_consumed(&mut self, consumer: HirId, target: TrackedValue) {
        self.places.consumed.entry(consumer).or_insert_with(|| <_>::default());

        debug!(?consumer, ?target, "mark_consumed");
        self.places.consumed.get_mut(&consumer).map(|places| places.insert(target));
    }

    fn borrow_place(&mut self, place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>) {
        self.places
            .borrowed
            .insert(TrackedValue::from_place_with_projections_allowed(place_with_id));

        // Ordinarily a value is consumed by it's parent, but in the special case of a
        // borrowed RValue, we create a reference that lives as long as the temporary scope
        // for that expression (typically, the innermost statement, but sometimes the enclosing
        // block). We record this fact here so that later in generator_interior
        // we can use the correct scope.
        //
        // We special case borrows through a dereference (`&*x`, `&mut *x` where `x` is
        // some rvalue expression), since these are essentially a copy of a pointer.
        // In other words, this borrow does not refer to the
        // temporary (`*x`), but to the referent (whatever `x` is a borrow of).
        //
        // We were considering that we might encounter problems down the line if somehow,
        // some part of the compiler were to look at this result and try to use it to
        // drive a borrowck-like analysis (this does not currently happen, as of this writing).
        // But even this should be fine, because the lifetime of the dereferenced reference
        // found in the rvalue is only significant as an intermediate 'link' to the value we
        // are producing, and we separately track whether that value is live over a yield.
        // Example:
        //
        // ```notrust
        // fn identity<T>(x: &mut T) -> &mut T { x }
        // let a: A = ...;
        // let y: &'y mut A = &mut *identity(&'a mut a);
        //                    ^^^^^^^^^^^^^^^^^^^^^^^^^ the borrow we are talking about
        // ```
        //
        // The expression `*identity(...)` is a deref of an rvalue,
        // where the `identity(...)` (the rvalue) produces a return type
        // of `&'rv mut A`, where `'a: 'rv`. We then assign this result to
        // `'y`, resulting in (transitively) `'a: 'y` (i.e., while `y` is in use,
        // `a` will be considered borrowed). Other parts of the code will ensure
        // that if `y` is live over a yield, `&'y mut A` appears in the generator
        // state. If `'y` is live, then any sound region analysis must conclude
        // that `'a` is also live. So if this causes a bug, blame some other
        // part of the code!
        let is_deref = place_with_id
            .place
            .projections
            .iter()
            .any(|Projection { kind, .. }| *kind == ProjectionKind::Deref);

        if let (false, PlaceBase::Rvalue) = (is_deref, place_with_id.place.base) {
            self.places.borrowed_temporaries.insert(place_with_id.hir_id);
        }
    }
}

impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
    fn consume(
        &mut self,
        place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
        diag_expr_id: HirId,
    ) {
        let hir = self.tcx.hir();
        let parent = match hir.opt_parent_id(place_with_id.hir_id) {
            Some(parent) => parent,
            None => place_with_id.hir_id,
        };
        debug!(
            "consume {:?}; diag_expr_id={}, using parent {}",
            place_with_id,
            hir.node_to_string(diag_expr_id),
            hir.node_to_string(parent)
        );
        place_with_id
            .try_into()
            .map_or((), |tracked_value| self.mark_consumed(parent, tracked_value));
    }

    fn borrow(
        &mut self,
        place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
        diag_expr_id: HirId,
        bk: rustc_middle::ty::BorrowKind,
    ) {
        debug!(
            "borrow: place_with_id = {place_with_id:#?}, diag_expr_id={diag_expr_id:#?}, \
            borrow_kind={bk:#?}"
        );

        self.borrow_place(place_with_id);
    }

    fn copy(
        &mut self,
        place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
        _diag_expr_id: HirId,
    ) {
        debug!("copy: place_with_id = {place_with_id:?}");

        self.places
            .borrowed
            .insert(TrackedValue::from_place_with_projections_allowed(place_with_id));

        // For copied we treat this mostly like a borrow except that we don't add the place
        // to borrowed_temporaries because the copy is consumed.
    }

    fn mutate(
        &mut self,
        assignee_place: &expr_use_visitor::PlaceWithHirId<'tcx>,
        diag_expr_id: HirId,
    ) {
        debug!("mutate {assignee_place:?}; diag_expr_id={diag_expr_id:?}");

        if assignee_place.place.base == PlaceBase::Rvalue
            && assignee_place.place.projections.is_empty()
        {
            // Assigning to an Rvalue is illegal unless done through a dereference. We would have
            // already gotten a type error, so we will just return here.
            return;
        }

        // If the type being assigned needs dropped, then the mutation counts as a borrow
        // since it is essentially doing `Drop::drop(&mut x); x = new_value;`.
        let ty = self.tcx.erase_regions(assignee_place.place.base_ty);
        if ty.needs_infer() {
            self.tcx.sess.delay_span_bug(
                self.tcx.hir().span(assignee_place.hir_id),
                &format!("inference variables in {ty}"),
            );
        } else if ty.needs_drop(self.tcx, self.param_env) {
            self.places
                .borrowed
                .insert(TrackedValue::from_place_with_projections_allowed(assignee_place));
        }
    }

    fn bind(
        &mut self,
        binding_place: &expr_use_visitor::PlaceWithHirId<'tcx>,
        diag_expr_id: HirId,
    ) {
        debug!("bind {binding_place:?}; diag_expr_id={diag_expr_id:?}");
    }

    fn fake_read(
        &mut self,
        place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
        cause: rustc_middle::mir::FakeReadCause,
        diag_expr_id: HirId,
    ) {
        debug!(
            "fake_read place_with_id={place_with_id:?}; cause={cause:?}; diag_expr_id={diag_expr_id:?}"
        );

        // fake reads happen in places like the scrutinee of a match expression.
        // we treat those as a borrow, much like a copy: the idea is that we are
        // transiently creating a `&T` ref that we can read from to observe the current
        // value (this `&T` is immediately dropped afterwards).
        self.borrow_place(place_with_id);
    }
}