Skip to main content

rustc_borrowck/polonius/legacy/
loan_invalidations.rs

1use std::ops::ControlFlow;
2
3use rustc_data_structures::graph::dominators::Dominators;
4use rustc_middle::bug;
5use rustc_middle::mir::visit::Visitor;
6use rustc_middle::mir::*;
7use rustc_middle::ty::TyCtxt;
8use tracing::debug;
9
10use super::{PoloniusFacts, PoloniusLocationTable};
11use crate::borrow_set::BorrowSet;
12use crate::path_utils::*;
13use crate::{
14    AccessDepth, Activation, ArtificialField, BorrowIndex, Deep, LocalMutationIsAllowed, Read,
15    ReadKind, ReadOrWrite, Reservation, Shallow, Write, WriteKind,
16};
17
18/// Emit `loan_invalidated_at` facts.
19pub(super) fn emit_loan_invalidations<'tcx>(
20    tcx: TyCtxt<'tcx>,
21    facts: &mut PoloniusFacts,
22    body: &Body<'tcx>,
23    location_table: &PoloniusLocationTable,
24    borrow_set: &BorrowSet<'tcx>,
25) {
26    let dominators = body.basic_blocks.dominators();
27    let mut visitor =
28        LoanInvalidationsGenerator { facts, borrow_set, tcx, location_table, body, dominators };
29    visitor.visit_body(body);
30}
31
32struct LoanInvalidationsGenerator<'a, 'tcx> {
33    tcx: TyCtxt<'tcx>,
34    facts: &'a mut PoloniusFacts,
35    body: &'a Body<'tcx>,
36    location_table: &'a PoloniusLocationTable,
37    dominators: &'a Dominators<BasicBlock>,
38    borrow_set: &'a BorrowSet<'tcx>,
39}
40
41/// Visits the whole MIR and generates `invalidates()` facts.
42/// Most of the code implementing this was stolen from `borrow_check/mod.rs`.
43impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> {
44    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
45        self.check_activations(location);
46
47        match &statement.kind {
48            StatementKind::Assign(box (lhs, rhs)) => {
49                self.consume_rvalue(location, rhs);
50
51                self.mutate_place(location, *lhs, Shallow(None));
52            }
53            StatementKind::FakeRead(box (_, _)) => {
54                // Only relevant for initialized/liveness/safety checks.
55            }
56            StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => {
57                self.consume_operand(location, op);
58            }
59            StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
60                src,
61                dst,
62                count,
63            })) => {
64                self.consume_operand(location, src);
65                self.consume_operand(location, dst);
66                self.consume_operand(location, count);
67            }
68            // Only relevant for mir typeck
69            StatementKind::AscribeUserType(..)
70            // Only relevant for liveness and unsafeck
71            | StatementKind::PlaceMention(..)
72            // Doesn't have any language semantics
73            | StatementKind::Coverage(..)
74            // Does not actually affect borrowck
75            | StatementKind::StorageLive(..) => {}
76            StatementKind::StorageDead(local) => {
77                self.access_place(
78                    location,
79                    Place::from(*local),
80                    (Shallow(None), Write(WriteKind::StorageDeadOrDrop)),
81                    LocalMutationIsAllowed::Yes,
82                );
83            }
84            StatementKind::ConstEvalCounter
85            | StatementKind::Nop
86            | StatementKind::BackwardIncompatibleDropHint { .. }
87            | StatementKind::SetDiscriminant { .. } => {
88                ::rustc_middle::util::bug::bug_fmt(format_args!("Statement not allowed in this MIR phase"))bug!("Statement not allowed in this MIR phase")
89            }
90        }
91
92        self.super_statement(statement, location);
93    }
94
95    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
96        self.check_activations(location);
97
98        match &terminator.kind {
99            TerminatorKind::SwitchInt { discr, targets: _ } => {
100                self.consume_operand(location, discr);
101            }
102            TerminatorKind::Drop {
103                place: drop_place,
104                target: _,
105                unwind: _,
106                replace,
107                drop: _,
108                async_fut: _,
109            } => {
110                let write_kind =
111                    if *replace { WriteKind::Replace } else { WriteKind::StorageDeadOrDrop };
112                self.access_place(
113                    location,
114                    *drop_place,
115                    (AccessDepth::Drop, Write(write_kind)),
116                    LocalMutationIsAllowed::Yes,
117                );
118            }
119            TerminatorKind::Call {
120                func,
121                args,
122                destination,
123                target: _,
124                unwind: _,
125                call_source: _,
126                fn_span: _,
127            } => {
128                self.consume_operand(location, func);
129                for arg in args {
130                    self.consume_operand(location, &arg.node);
131                }
132                self.mutate_place(location, *destination, Deep);
133            }
134            TerminatorKind::TailCall { func, args, .. } => {
135                self.consume_operand(location, func);
136                for arg in args {
137                    self.consume_operand(location, &arg.node);
138                }
139            }
140            TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => {
141                self.consume_operand(location, cond);
142                use rustc_middle::mir::AssertKind;
143                if let AssertKind::BoundsCheck { len, index } = &**msg {
144                    self.consume_operand(location, len);
145                    self.consume_operand(location, index);
146                }
147            }
148            TerminatorKind::Yield { value, resume, resume_arg, drop: _ } => {
149                self.consume_operand(location, value);
150
151                // Invalidate all borrows of local places
152                let borrow_set = self.borrow_set;
153                let resume = self.location_table.start_index(resume.start_location());
154                for (i, data) in borrow_set.iter_enumerated() {
155                    if borrow_of_local_data(data.borrowed_place) {
156                        self.facts.loan_invalidated_at.push((resume, i));
157                    }
158                }
159
160                self.mutate_place(location, *resume_arg, Deep);
161            }
162            TerminatorKind::UnwindResume
163            | TerminatorKind::Return
164            | TerminatorKind::CoroutineDrop => {
165                // Invalidate all borrows of local places
166                let borrow_set = self.borrow_set;
167                let start = self.location_table.start_index(location);
168                for (i, data) in borrow_set.iter_enumerated() {
169                    if borrow_of_local_data(data.borrowed_place) {
170                        self.facts.loan_invalidated_at.push((start, i));
171                    }
172                }
173            }
174            TerminatorKind::InlineAsm {
175                asm_macro: _,
176                template: _,
177                operands,
178                options: _,
179                line_spans: _,
180                targets: _,
181                unwind: _,
182            } => {
183                for op in operands {
184                    match op {
185                        InlineAsmOperand::In { reg: _, value } => {
186                            self.consume_operand(location, value);
187                        }
188                        InlineAsmOperand::Out { reg: _, late: _, place, .. } => {
189                            if let &Some(place) = place {
190                                self.mutate_place(location, place, Shallow(None));
191                            }
192                        }
193                        InlineAsmOperand::InOut { reg: _, late: _, in_value, out_place } => {
194                            self.consume_operand(location, in_value);
195                            if let &Some(out_place) = out_place {
196                                self.mutate_place(location, out_place, Shallow(None));
197                            }
198                        }
199                        InlineAsmOperand::Const { value: _ }
200                        | InlineAsmOperand::SymFn { value: _ }
201                        | InlineAsmOperand::SymStatic { def_id: _ }
202                        | InlineAsmOperand::Label { target_index: _ } => {}
203                    }
204                }
205            }
206            TerminatorKind::Goto { target: _ }
207            | TerminatorKind::UnwindTerminate(_)
208            | TerminatorKind::Unreachable
209            | TerminatorKind::FalseEdge { real_target: _, imaginary_target: _ }
210            | TerminatorKind::FalseUnwind { real_target: _, unwind: _ } => {
211                // no data used, thus irrelevant to borrowck
212            }
213        }
214
215        self.super_terminator(terminator, location);
216    }
217}
218
219impl<'a, 'tcx> LoanInvalidationsGenerator<'a, 'tcx> {
220    /// Simulates mutation of a place.
221    fn mutate_place(&mut self, location: Location, place: Place<'tcx>, kind: AccessDepth) {
222        self.access_place(
223            location,
224            place,
225            (kind, Write(WriteKind::Mutate)),
226            LocalMutationIsAllowed::ExceptUpvars,
227        );
228    }
229
230    /// Simulates consumption of an operand.
231    fn consume_operand(&mut self, location: Location, operand: &Operand<'tcx>) {
232        match *operand {
233            Operand::Copy(place) => {
234                self.access_place(
235                    location,
236                    place,
237                    (Deep, Read(ReadKind::Copy)),
238                    LocalMutationIsAllowed::No,
239                );
240            }
241            Operand::Move(place) => {
242                self.access_place(
243                    location,
244                    place,
245                    (Deep, Write(WriteKind::Move)),
246                    LocalMutationIsAllowed::Yes,
247                );
248            }
249            Operand::Constant(_) | Operand::RuntimeChecks(_) => {}
250        }
251    }
252
253    // Simulates consumption of an rvalue
254    fn consume_rvalue(&mut self, location: Location, rvalue: &Rvalue<'tcx>) {
255        match rvalue {
256            &Rvalue::Ref(_ /*rgn*/, bk, place) => {
257                let access_kind = match bk {
258                    BorrowKind::Fake(FakeBorrowKind::Shallow) => {
259                        (Shallow(Some(ArtificialField::FakeBorrow)), Read(ReadKind::Borrow(bk)))
260                    }
261                    BorrowKind::Shared | BorrowKind::Fake(FakeBorrowKind::Deep) => {
262                        (Deep, Read(ReadKind::Borrow(bk)))
263                    }
264                    BorrowKind::Mut { .. } => {
265                        let wk = WriteKind::MutableBorrow(bk);
266                        if bk.is_two_phase_borrow() {
267                            (Deep, Reservation(wk))
268                        } else {
269                            (Deep, Write(wk))
270                        }
271                    }
272                };
273
274                self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
275            }
276
277            &Rvalue::RawPtr(kind, place) => {
278                let access_kind = match kind {
279                    RawPtrKind::Mut => (
280                        Deep,
281                        Write(WriteKind::MutableBorrow(BorrowKind::Mut {
282                            kind: MutBorrowKind::Default,
283                        })),
284                    ),
285                    RawPtrKind::Const => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
286                    RawPtrKind::FakeForPtrMetadata => {
287                        (Shallow(Some(ArtificialField::ArrayLength)), Read(ReadKind::Copy))
288                    }
289                };
290
291                self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
292            }
293
294            Rvalue::ThreadLocalRef(_) => {}
295
296            Rvalue::Use(operand, _)
297            | Rvalue::Repeat(operand, _)
298            | Rvalue::UnaryOp(_ /*un_op*/, operand)
299            | Rvalue::Cast(_ /*cast_kind*/, operand, _ /*ty*/) => {
300                self.consume_operand(location, operand)
301            }
302
303            &Rvalue::Discriminant(place) => {
304                self.access_place(
305                    location,
306                    place,
307                    (Shallow(None), Read(ReadKind::Copy)),
308                    LocalMutationIsAllowed::No,
309                );
310            }
311
312            Rvalue::BinaryOp(_bin_op, box (operand1, operand2)) => {
313                self.consume_operand(location, operand1);
314                self.consume_operand(location, operand2);
315            }
316
317            Rvalue::Aggregate(_, operands) => {
318                for operand in operands {
319                    self.consume_operand(location, operand);
320                }
321            }
322
323            Rvalue::WrapUnsafeBinder(op, _) => {
324                self.consume_operand(location, op);
325            }
326
327            Rvalue::CopyForDeref(_) => ::rustc_middle::util::bug::bug_fmt(format_args!("`CopyForDeref` in borrowck"))bug!("`CopyForDeref` in borrowck"),
328        }
329    }
330
331    /// Simulates an access to a place.
332    fn access_place(
333        &mut self,
334        location: Location,
335        place: Place<'tcx>,
336        kind: (AccessDepth, ReadOrWrite),
337        _is_local_mutation_allowed: LocalMutationIsAllowed,
338    ) {
339        let (sd, rw) = kind;
340        // note: not doing check_access_permissions checks because they don't generate invalidates
341        self.check_access_for_conflict(location, place, sd, rw);
342    }
343
344    fn check_access_for_conflict(
345        &mut self,
346        location: Location,
347        place: Place<'tcx>,
348        sd: AccessDepth,
349        rw: ReadOrWrite,
350    ) {
351        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs:351",
                        "rustc_borrowck::polonius::legacy::loan_invalidations",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_borrowck/src/polonius/legacy/loan_invalidations.rs"),
                        ::tracing_core::__macro_support::Option::Some(351u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_borrowck::polonius::legacy::loan_invalidations"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("check_access_for_conflict(location={0:?}, place={1:?}, sd={2:?}, rw={3:?})",
                                                    location, place, sd, rw) as &dyn Value))])
            });
    } else { ; }
};debug!(
352            "check_access_for_conflict(location={:?}, place={:?}, sd={:?}, rw={:?})",
353            location, place, sd, rw,
354        );
355        each_borrow_involving_path(
356            self,
357            self.tcx,
358            self.body,
359            (sd, place),
360            self.borrow_set,
361            |_| true,
362            |this, borrow_index, borrow| {
363                match (rw, borrow.kind) {
364                    // Obviously an activation is compatible with its own
365                    // reservation (or even prior activating uses of same
366                    // borrow); so don't check if they interfere.
367                    //
368                    // NOTE: *reservations* do conflict with themselves;
369                    // thus aren't injecting unsoundness w/ this check.)
370                    (Activation(_, activating), _) if activating == borrow_index => {
371                        // Activating a borrow doesn't generate any invalidations, since we
372                        // have already taken the reservation
373                    }
374
375                    (Read(_), BorrowKind::Fake(_) | BorrowKind::Shared)
376                    | (
377                        Read(ReadKind::Borrow(BorrowKind::Fake(FakeBorrowKind::Shallow))),
378                        BorrowKind::Mut { .. },
379                    ) => {
380                        // Reads don't invalidate shared or shallow borrows
381                    }
382
383                    (Read(_), BorrowKind::Mut { .. }) => {
384                        // Reading from mere reservations of mutable-borrows is OK.
385                        if !is_active(this.dominators, borrow, location) {
386                            // If the borrow isn't active yet, reads don't invalidate it
387                            if !borrow.kind.is_two_phase_borrow() {
    ::core::panicking::panic("assertion failed: borrow.kind.is_two_phase_borrow()")
};assert!(borrow.kind.is_two_phase_borrow());
388                            return ControlFlow::Continue(());
389                        }
390
391                        // Unique and mutable borrows are invalidated by reads from any
392                        // involved path
393                        this.emit_loan_invalidated_at(borrow_index, location);
394                    }
395
396                    (Reservation(_) | Activation(_, _) | Write(_), _) => {
397                        // unique or mutable borrows are invalidated by writes.
398                        // Reservations count as writes since we need to check
399                        // that activating the borrow will be OK
400                        // FIXME(bob_twinkles) is this actually the right thing to do?
401                        this.emit_loan_invalidated_at(borrow_index, location);
402                    }
403                }
404                ControlFlow::Continue(())
405            },
406        );
407    }
408
409    /// Generates a new `loan_invalidated_at(L, B)` fact.
410    fn emit_loan_invalidated_at(&mut self, b: BorrowIndex, l: Location) {
411        let lidx = self.location_table.start_index(l);
412        self.facts.loan_invalidated_at.push((lidx, b));
413    }
414
415    fn check_activations(&mut self, location: Location) {
416        // Two-phase borrow support: For each activation that is newly
417        // generated at this statement, check if it interferes with
418        // another borrow.
419        for &borrow_index in self.borrow_set.activations_at_location(location) {
420            let borrow = &self.borrow_set[borrow_index];
421
422            // only mutable borrows should be 2-phase
423            if !match borrow.kind {
            BorrowKind::Shared | BorrowKind::Fake(_) => false,
            BorrowKind::Mut { .. } => true,
        } {
    ::core::panicking::panic("assertion failed: match borrow.kind {\n    BorrowKind::Shared | BorrowKind::Fake(_) => false,\n    BorrowKind::Mut { .. } => true,\n}")
};assert!(match borrow.kind {
424                BorrowKind::Shared | BorrowKind::Fake(_) => false,
425                BorrowKind::Mut { .. } => true,
426            });
427
428            self.access_place(
429                location,
430                borrow.borrowed_place,
431                (Deep, Activation(WriteKind::MutableBorrow(borrow.kind), borrow_index)),
432                LocalMutationIsAllowed::No,
433            );
434
435            // We do not need to call `check_if_path_or_subpath_is_moved`
436            // again, as we already called it when we made the
437            // initial reservation.
438        }
439    }
440}