Skip to main content

rustc_mir_transform/
lint.rs

1//! This pass statically detects code which has undefined behaviour or is likely to be erroneous.
2//! It can be used to locate problems in MIR building or optimizations. It assumes that all code
3//! can be executed, so it has false positives.
4
5use std::borrow::Cow;
6
7use rustc_data_structures::fx::FxHashSet;
8use rustc_index::bit_set::DenseBitSet;
9use rustc_middle::mir::visit::{PlaceContext, VisitPlacesWith, Visitor};
10use rustc_middle::mir::*;
11use rustc_middle::ty::TyCtxt;
12use rustc_mir_dataflow::impls::{MaybeStorageDead, MaybeStorageLive, always_storage_live_locals};
13use rustc_mir_dataflow::{Analysis, ResultsCursor};
14
15pub(super) fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
16    let always_live_locals = &always_storage_live_locals(body);
17
18    let maybe_storage_live = MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
19        .iterate_to_fixpoint(tcx, body, None)
20        .into_results_cursor(body);
21
22    let maybe_storage_dead = MaybeStorageDead::new(Cow::Borrowed(always_live_locals))
23        .iterate_to_fixpoint(tcx, body, None)
24        .into_results_cursor(body);
25
26    let mut lint = Lint {
27        tcx,
28        when,
29        body,
30        is_fn_like: tcx.def_kind(body.source.def_id()).is_fn_like(),
31        always_live_locals,
32        maybe_storage_live,
33        maybe_storage_dead,
34        places: Default::default(),
35    };
36    for (bb, data) in traversal::reachable(body) {
37        lint.visit_basic_block_data(bb, data);
38    }
39}
40
41struct Lint<'a, 'tcx> {
42    tcx: TyCtxt<'tcx>,
43    when: String,
44    body: &'a Body<'tcx>,
45    is_fn_like: bool,
46    always_live_locals: &'a DenseBitSet<Local>,
47    maybe_storage_live: ResultsCursor<'a, 'tcx, MaybeStorageLive<'a>>,
48    maybe_storage_dead: ResultsCursor<'a, 'tcx, MaybeStorageDead<'a>>,
49    places: FxHashSet<PlaceRef<'tcx>>,
50}
51
52impl<'a, 'tcx> Lint<'a, 'tcx> {
53    #[track_caller]
54    fn fail(&self, location: Location, msg: impl AsRef<str>) {
55        let span = self.body.source_info(location).span;
56        self.tcx.sess.dcx().span_delayed_bug(
57            span,
58            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("broken MIR in {0:?} ({1}) at {2:?}:\n{3}",
                self.body.source.instance, self.when, location, msg.as_ref()))
    })format!(
59                "broken MIR in {:?} ({}) at {:?}:\n{}",
60                self.body.source.instance,
61                self.when,
62                location,
63                msg.as_ref()
64            ),
65        );
66    }
67}
68
69impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
70    fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
71        if context.is_use() {
72            self.maybe_storage_dead.seek_after_primary_effect(location);
73            if self.maybe_storage_dead.get().contains(local) {
74                self.fail(location, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("use of local {0:?}, which has no storage here",
                local))
    })format!("use of local {local:?}, which has no storage here"));
75            }
76        }
77    }
78
79    fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
80        match &statement.kind {
81            StatementKind::Assign(box (dest, rvalue)) => {
82                let forbid_aliasing = match rvalue {
83                    Rvalue::Use(..)
84                    | Rvalue::CopyForDeref(..)
85                    | Rvalue::Repeat(..)
86                    | Rvalue::Aggregate(..)
87                    | Rvalue::Cast(..)
88                    | Rvalue::WrapUnsafeBinder(..) => true,
89                    Rvalue::ThreadLocalRef(..)
90                    | Rvalue::UnaryOp(..)
91                    | Rvalue::BinaryOp(..)
92                    | Rvalue::Ref(..)
93                    | Rvalue::RawPtr(..)
94                    | Rvalue::Discriminant(..) => false,
95                };
96                // The sides of an assignment must not alias.
97                if forbid_aliasing {
98                    VisitPlacesWith(|src: Place<'tcx>, _| {
99                        if *dest == src
100                            || (dest.local == src.local
101                                && !dest.is_indirect()
102                                && !src.is_indirect())
103                        {
104                            self.fail(
105                                location,
106                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("encountered `{0:?}` statement with overlapping memory",
                statement))
    })format!(
107                                    "encountered `{statement:?}` statement with overlapping memory"
108                                ),
109                            );
110                        }
111                    })
112                    .visit_rvalue(rvalue, location);
113                }
114            }
115            StatementKind::StorageLive(local) => {
116                self.maybe_storage_live.seek_before_primary_effect(location);
117                if self.maybe_storage_live.get().contains(*local) {
118                    self.fail(
119                        location,
120                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("StorageLive({0:?}) which already has storage here",
                local))
    })format!("StorageLive({local:?}) which already has storage here"),
121                    );
122                }
123            }
124            _ => {}
125        }
126
127        self.super_statement(statement, location);
128    }
129
130    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
131        match &terminator.kind {
132            TerminatorKind::Return => {
133                if self.is_fn_like {
134                    self.maybe_storage_live.seek_after_primary_effect(location);
135                    for local in self.maybe_storage_live.get().iter() {
136                        if !self.always_live_locals.contains(local) {
137                            self.fail(
138                                location,
139                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("local {0:?} still has storage when returning from function",
                local))
    })format!(
140                                    "local {local:?} still has storage when returning from function"
141                                ),
142                            );
143                        }
144                    }
145                }
146            }
147            TerminatorKind::Call { args, destination, .. } => {
148                // The call destination place and Operand::Move place used as an argument might be
149                // passed by a reference to the callee. Consequently they must be non-overlapping.
150                // Currently this simply checks for duplicate places.
151                self.places.clear();
152                self.places.insert(destination.as_ref());
153                let mut has_duplicates = false;
154                for arg in args {
155                    if let Operand::Move(place) = &arg.node {
156                        has_duplicates |= !self.places.insert(place.as_ref());
157                    }
158                }
159                if has_duplicates {
160                    self.fail(
161                        location,
162                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("encountered overlapping memory in `Move` arguments to `Call` terminator: {0:?}",
                terminator.kind))
    })format!(
163                            "encountered overlapping memory in `Move` arguments to `Call` terminator: {:?}",
164                            terminator.kind,
165                        ),
166                    );
167                }
168            }
169            _ => {}
170        }
171
172        self.super_terminator(terminator, location);
173    }
174}