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
// `#![no_std]`: these arrive with the standard prelude and name no path, so a `std::`
// search cannot see them - and a `#[derive]` can use them without the name appearing
// in this file at all, which is why they are not trimmed by inspection.
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use crate::rustc_hir::def::DefKind;
use crate::rustc_lint_defs::builtin::UNREACHABLE_CODE;
use crate::rustc_middle::mir::*;
use crate::rustc_middle::ty::TyCtxt;
use crate::rustc_mir_transform::PassPolicy;
use crate::rustc_mir_transform::diagnostics::UnreachableDueToUninhabited;
/// Lint unreachable code due to uninhabited values from function calls,
/// and remove return edges from those calls.
pub(super) struct LintAndRemoveUninhabited;
impl<'tcx> crate::rustc_mir_transform::MirPass<'tcx> for LintAndRemoveUninhabited {
#[tracing::instrument(level = "debug", skip_all)]
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id().expect_local();
tracing::debug!(?def_id);
let parent_module = tcx.parent_module_from_def_id(def_id);
let typing_env = body.typing_env(tcx);
// check if the function's return type is inhabited
// this was added here because of this regression
// https://github.com/rust-lang/rust/issues/149571
let return_ty_is_inhabited = matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn)
&& body.local_decls[RETURN_PLACE].ty.is_inhabited_from(tcx, parent_module, typing_env);
let mut lints = vec![];
for bbdata in body.basic_blocks.as_mut() {
let term = bbdata.terminator_mut();
let TerminatorKind::Call { ref mut target, destination, .. } = term.kind else {
continue;
};
let Some(target_bb) = *target else { continue };
let ty = destination.ty(&body.local_decls, tcx).ty;
let ty_is_inhabited = ty.is_inhabited_from(tcx, parent_module, typing_env);
if !ty_is_inhabited {
// Unreachable code warnings are already emitted during type checking.
// However, during type checking, full type information is being
// calculated but not yet available, so the check for diverging
// expressions due to uninhabited result types is pretty crude and
// only checks whether ty.is_never(). Here, we have full type
// information available and can issue warnings for less obviously
// uninhabited types (e.g. empty enums). The check above is used so
// that we do not emit the same warning twice if the uninhabited type
// is indeed `!`.
if !ty.is_never() && return_ty_is_inhabited {
lints.push((target_bb, ty, term.source_info.span));
}
// The presence or absence of a return edge affects control-flow sensitive
// MIR checks and ultimately whether code is accepted or not. We can only
// omit the return edge if a return type is visibly uninhabited to a module
// that makes the call.
*target = None;
}
}
for (target_bb, orig_ty, orig_span) in lints {
if orig_span.in_external_macro(tcx.sess.source_map()) {
continue;
}
let Some((target_loc, descr)) = find_unreachable_code_from(target_bb, body) else {
continue;
};
let lint_root = body.source_scopes[target_loc.scope]
.local_data
.as_ref()
.unwrap_crate_local()
.lint_root;
tcx.emit_node_span_lint(
UNREACHABLE_CODE,
lint_root,
target_loc.span,
UnreachableDueToUninhabited {
expr: target_loc.span,
orig: orig_span,
descr,
ty: orig_ty,
},
);
}
}
fn policy(&self, _sess: &crate::rustc_session::Session) -> PassPolicy {
// Removing visibly uninhabited return edges determines the control flow seen by MIR checks.
// Cannot remove UB: removing the return edge would *introduce* UB if the call actually returned.
PassPolicy::Required
}
}
/// Starting at a target unreachable block, find some user code to lint as unreachable
#[tracing::instrument(level = "debug", skip(body), ret)]
fn find_unreachable_code_from<'tcx>(
bb: BasicBlock,
body: &Body<'tcx>,
) -> Option<(SourceInfo, &'static str)> {
let bbdata = &body.basic_blocks[bb];
for stmt in &bbdata.statements {
match &stmt.kind {
// Ignore the implicit `()` return place assignment for unit functions/blocks
StatementKind::Assign(assign)
if let (_, Rvalue::Use(Operand::Constant(const_), _)) = &**assign
&& const_.ty().is_unit() =>
{
continue;
}
// Ignore return value plumbing. After a call returning a non-`!`
// uninhabited type, a tail expression can be unreachable while
// still being needed to satisfy the surrounding return type.
StatementKind::Assign(assign) if assign.0.as_local() == Some(RETURN_PLACE) => {
continue;
}
// Ignore statements inserted by MIR building that do not correspond to user code.
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::BackwardIncompatibleDropHint { .. } => {
continue;
}
StatementKind::FakeRead(..) => return Some((stmt.source_info, "definition")),
_ => return Some((stmt.source_info, "expression")),
}
}
let term = bbdata.terminator();
match term.kind {
// The user does not care for `goto` and compiler-generated drops. If the target block is
// only reachable through those terminators, continue searching there.
TerminatorKind::Goto { target } | TerminatorKind::Drop { target, .. } => {
if &body.basic_blocks.predecessors()[target][..] == &[bb] {
find_unreachable_code_from(target, body)
} else {
None
}
}
TerminatorKind::Return => None,
_ => Some((term.source_info, "expression")),
}
}