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
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_span::symbol::sym;
use rustc_span::Span;
use tracing::trace;

use super::check::Qualifs;
use super::ops::{self, NonConstOp};
use super::qualifs::{NeedsNonConstDrop, Qualif};
use super::ConstCx;
use crate::check_consts::rustc_allow_const_fn_unstable;

/// Returns `true` if we should use the more precise live drop checker that runs after drop
/// elaboration.
pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool {
    // Const-stable functions must always use the stable live drop checker...
    if ccx.is_const_stable_const_fn() {
        // ...except if they have the feature flag set via `rustc_allow_const_fn_unstable`.
        return rustc_allow_const_fn_unstable(
            ccx.tcx,
            ccx.body.source.def_id().expect_local(),
            sym::const_precise_live_drops,
        );
    }

    ccx.tcx.features().const_precise_live_drops
}

/// Look for live drops in a const context.
///
/// This is separate from the rest of the const checking logic because it must run after drop
/// elaboration.
pub fn check_live_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
    let def_id = body.source.def_id().expect_local();
    let const_kind = tcx.hir().body_const_context(def_id);
    if const_kind.is_none() {
        return;
    }

    if tcx.has_attr(def_id, sym::rustc_do_not_const_check) {
        return;
    }

    let ccx = ConstCx { body, tcx, const_kind, param_env: tcx.param_env(def_id) };
    if !checking_enabled(&ccx) {
        return;
    }

    let mut visitor = CheckLiveDrops { ccx: &ccx, qualifs: Qualifs::default() };

    visitor.visit_body(body);
}

struct CheckLiveDrops<'mir, 'tcx> {
    ccx: &'mir ConstCx<'mir, 'tcx>,
    qualifs: Qualifs<'mir, 'tcx>,
}

// So we can access `body` and `tcx`.
impl<'mir, 'tcx> std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
    type Target = ConstCx<'mir, 'tcx>;

    fn deref(&self) -> &Self::Target {
        self.ccx
    }
}

impl<'tcx> CheckLiveDrops<'_, 'tcx> {
    fn check_live_drop(&self, span: Span, dropped_ty: Ty<'tcx>) {
        ops::LiveDrop { dropped_at: None, dropped_ty }.build_error(self.ccx, span).emit();
    }
}

impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
    fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) {
        trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);

        // Ignore drop terminators in cleanup blocks.
        if block.is_cleanup {
            return;
        }

        self.super_basic_block_data(bb, block);
    }

    fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
        trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);

        match &terminator.kind {
            mir::TerminatorKind::Drop { place: dropped_place, .. } => {
                let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;

                if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
                    // Instead of throwing a bug, we just return here. This is because we have to
                    // run custom `const Drop` impls.
                    return;
                }

                if dropped_place.is_indirect() {
                    self.check_live_drop(terminator.source_info.span, dropped_ty);
                    return;
                }

                // Drop elaboration is not precise enough to accept code like
                // `tests/ui/consts/control-flow/drop-pass.rs`; e.g., when an `Option<Vec<T>>` is
                // initialized with `None` and never changed, it still emits drop glue.
                // Hence we additionally check the qualifs here to allow more code to pass.
                if self.qualifs.needs_non_const_drop(self.ccx, dropped_place.local, location) {
                    // Use the span where the dropped local was declared for the error.
                    let span = self.body.local_decls[dropped_place.local].source_info.span;
                    self.check_live_drop(span, dropped_ty);
                }
            }

            mir::TerminatorKind::UnwindTerminate(_)
            | mir::TerminatorKind::Call { .. }
            | mir::TerminatorKind::TailCall { .. }
            | mir::TerminatorKind::Assert { .. }
            | mir::TerminatorKind::FalseEdge { .. }
            | mir::TerminatorKind::FalseUnwind { .. }
            | mir::TerminatorKind::CoroutineDrop
            | mir::TerminatorKind::Goto { .. }
            | mir::TerminatorKind::InlineAsm { .. }
            | mir::TerminatorKind::UnwindResume
            | mir::TerminatorKind::Return
            | mir::TerminatorKind::SwitchInt { .. }
            | mir::TerminatorKind::Unreachable
            | mir::TerminatorKind::Yield { .. } => {}
        }
    }
}