Skip to content

Commit 65318da

Browse files
committed
Add a bunch of comments
1 parent a8ce823 commit 65318da

File tree

1 file changed

+236
-19
lines changed

1 file changed

+236
-19
lines changed

src/librustc_typeck/check/never_compat.rs

+236-19
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,162 @@ use rustc_data_structures::fx::FxHashMap;
99
use rustc_hir::HirId;
1010

1111
/// Code to detect cases where using `!` (never-type) fallback instead of `()` fallback
12-
/// may result in the introduction of undefined behavior
12+
/// may result in the introduction of undefined behavior;
1313
///
14+
/// TL;DR: We look for method calls whose arguments are all inhabited, but
15+
/// that have a generic parameter (e.g. `T` in `fn foo<T>() { ... } `)
16+
/// that's uninhabited due to fallback. We emit an error, forcing
17+
/// users to add explicit type annotations indicating whether they
18+
/// actually want the `!` type, or something else.
19+
///
20+
/// Background: When we first tried to enable never-type fallback, it was found to
21+
/// cause code in the 'obj' crate to segfault. The root cause was a piece
22+
/// of code that looked like this:
23+
///
24+
/// ```rust
25+
///fn unconstrained_return<T>() -> Result<T, String> {
26+
/// let ffi: fn() -> T = transmute(some_pointer);
27+
/// Ok(ffi())
28+
/// }
29+
///fn foo() {
30+
/// match unconstrained_return::<_>() {
31+
/// Ok(x) => x, // `x` has type `_`, which is unconstrained
32+
/// Err(s) => panic!(s), // … except for unifying with the type of `panic!()`
33+
/// // so that both `match` arms have the same type.
34+
/// // Therefore `_` resolves to `!` and we "return" an `Ok(!)` value.
35+
/// };
36+
///}
37+
/// ```
38+
///
39+
/// Previously, the return type of `panic!()` would end up as `()` due to fallback
40+
/// (even though `panic()` is declared as returning `!`). This meant that
41+
/// the function pointer would get transmuted to `fn() -> ()`.
42+
///
43+
/// With never-type fallback enabled, the function pointer was transmuted
44+
/// to `fn() -> !`, despite the fact that it actually returned. This lead
45+
/// to undefined behavior, since we actually "produced" an instance of `!`
46+
/// at runtime.
47+
///
48+
/// We want to prevent this code from compiling, unless the user provides
49+
/// explicit type annotations indicating that they want the `!` type to be used.
50+
///
51+
/// However, we want code like this to continue working:
52+
///
53+
/// ```rust
54+
/// fn foo(e: !) -> Box<dyn std::error::Error> {
55+
/// Box::<_>::new(e)
56+
/// }```
57+
///
58+
/// From the perspective of fallback, these two snippets of code are very
59+
/// similar. We have some expression that starts out with type `!`, which
60+
/// we replace with a divergent inference variable. When fallback runs,
61+
/// we unify the inference variable with `!`, which leads to other
62+
/// expressions getting a type of `!` as well (`Box<!>` in this case),
63+
/// becoming uninhabited as well.
64+
///
65+
/// Our task is to distinguish things that are "legitimately" uninhabited,
66+
/// (such as `Box<!>` in the second example), from things that are
67+
/// "suspiciously" uninhabited and may lead to undefined behavior
68+
/// (the `Result<!, !>` in the first example).
69+
///
70+
/// The key difference between these two snippets of code turns out
71+
/// to be the *arguments* used in a function call. In the first example,
72+
/// we call `unconstrained_return` with no arguments, and an inferred
73+
/// type parameter of `!` (a.k.a `unconstrained_return::<!>()`).
74+
///
75+
/// In the second example, we call `Box::new(!)`, again inferring
76+
/// the type parameter to `!`. Since at least one argument to `Box::new`,
77+
/// is uninhabited, we know that this call is *statically unreachable* -
78+
/// if it were ever executed, we would have instantaited an uninhabited
79+
/// type for a parameter, which is impossible. This means that while
80+
/// the fallback to `!` has affected the type-checking of this call,
81+
/// it won't actually affect the runtime behavior of the call, since
82+
/// the call can never be executed.
83+
///
84+
/// In the first example, the call to `unconstrained_return::<!>()` is
85+
/// still (potentially) live, since it trivially has no uninhabited arguments
86+
/// (it has no arguments at all). This means that our fallback to `!` may
87+
/// have changed the runtime behavior of `unconstrained_return` (in this case,
88+
/// it did) - we may actually execute the call at runtime, but with an uninhabited
89+
/// type parameter that the user did not intend to provide.
90+
///
91+
/// This distinction forms the basis for our lint. We look for
92+
/// any function/method calls that meet the following requirements:
93+
///
94+
/// * Unresolved inference variables are present in the generic substs prior to fallback
95+
/// * All arguments are inhabited
96+
/// * After fallback, at least one generic subsst is uninhabited.
97+
///
98+
/// Let's break down each of these requirements:
99+
///
100+
/// 1. We only look at method calls, not other expressions.
101+
/// This is because the issue boils down to a generic subst being
102+
/// uninhabited due to fallback. Generic parameters can only be used
103+
/// in two places - as arguments to types, and arguments to methods.
104+
/// We don't care about types - the only relevant expression would be
105+
/// a constructor (e.g. `MyStruct { a: true, b: 25 }`). In and of itself,
106+
/// constructing a type this way (after all field values are evaluated)
107+
/// can never cause undefined behavior.
108+
///
109+
/// Methods calls, on the other hand, might cause undefined behavior
110+
/// due to how they use their generic paramters. Note that this
111+
/// still true if the undefineed behavior occurs in the same function
112+
/// where fallback occurs - there still must be a call `transmute`
113+
/// or some other intrinsic that allows 'instantating' an `!` instance
114+
/// somehow.
115+
///
116+
/// 2. Only function calls with inference variables in their generic
117+
/// substs can possibly be affected by fallback - if all types are resolved,
118+
/// then fallback cannot possibly have any effect.
119+
///
120+
/// 3. If any arguments are uninhabited, then the function call cannot ever
121+
/// cause undefined behavior, since it can never be executed. This check
122+
/// ensures that we don't lint on `Box::<_>::new(!)` - even though
123+
/// we're inferring a generic subst (`_`) to `!`, it has no runtime
124+
/// effect.
125+
///
126+
/// 4. If a generic substs is uninhabited after fallback, then we have
127+
/// the potential for undefined behavior. Note that this check can
128+
/// have false positives - if a generic argument was already uninhabited
129+
/// prior to fallback, we might end up triggering the lint even if fallback
130+
/// didn't cause any generic substs to become uninhabited. However, this isn't
131+
/// a big deal for a few reasons:
132+
///
133+
/// * This lint should be incredibly rare (as of the time of writing, only
134+
/// `obj` has run into the relevant issue).
135+
/// * Even if fallback didn't cause anything to become uninhabited
136+
/// (e.g. a `*const _` being inferred to `*const !`), it's still influencing
137+
/// the generic substs of a potentially live (all arguments inhabited) function
138+
/// call. This is arguably something worth linting against, since the user
139+
/// might not be aware that a live call is being affected by a seemingly
140+
/// unrelated expression.
141+
///
142+
/// * It should always be possible to resolve this lint while keeping the
143+
/// existing behavior, simply by adding explicit type annotations. This
144+
/// ensures that fallback never runs (since the type variable will
145+
/// actually be constrained to `!` or something else). At worst,
146+
/// we will force users to write some arguably unecessary type annotation,
147+
/// but we will not permanently break any code.
14148
149+
/// The main interface to this module.
150+
/// It exposes two hooks: `pre_fallback` and `post_fallback`,
151+
/// which are invoked by `typeck_tables_of_with_fallback` respectively before
152+
/// and after fallback is run.
15153
pub struct NeverCompatHandler<'tcx> {
154+
/// A map from method call `HirId`'s to `InferredPaths`.
155+
/// This is computed from `FnCtxt.inferred_paths`, which
156+
/// is in turn populated during typecheck.
16157
unresolved_paths: FxHashMap<HirId, InferredPath<'tcx>>,
158+
/// All divering type variables that were unconstrained
159+
/// prior to fallback. We use this to help generate
160+
/// better diagnostics by determining which
161+
/// inference variables were equated with a diverging
162+
/// fallback variable.
17163
unconstrained_diverging: Vec<Ty<'tcx>>,
18164
}
19165

166+
/// A simpler helper to collect all `InferTy::TyVar`s found
167+
/// in a `TypeFoldable`.
20168
struct TyVarFinder<'a, 'tcx> {
21169
infcx: &'a InferCtxt<'a, 'tcx>,
22170
vars: Vec<Ty<'tcx>>,
@@ -34,64 +182,104 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TyVarFinder<'a, 'tcx> {
34182
}
35183
}
36184

185+
/// The core logic of this check. Given
186+
/// an `InferredPath`, we determine
187+
/// if this method call is "questionable" using the criteria
188+
/// described at the top of the module.
189+
///
190+
/// If the method call is "questionable", and should
191+
/// be linted, we return Ok(tys), where `tys`
192+
/// is a list of all inference variables involved
193+
/// with the generic paramter affected by fallback.
194+
///
195+
/// Otherwise, we return None
37196
fn find_questionable_call<'a, 'tcx>(
38197
path: &'a InferredPath<'tcx>,
39198
fcx: &FnCtxt<'a, 'tcx>,
40199
) -> Option<&'a [Ty<'tcx>]> {
41200
let tcx = fcx.tcx;
42201
let ty = fcx.infcx.resolve_vars_if_possible(&path.ty);
43-
debug!("post_fallback: Fully resolved ty: {:?}", ty);
202+
debug!("find_questionable_call: Fully resolved ty: {:?}", ty);
44203

45204
let ty = ty.unwrap_or_else(|| bug!("Missing ty in path: {:?}", path));
46205

206+
/// Check that this InferredPath actually corresponds to a method
207+
/// invocation. Currently, type-checking resolves generic paths
208+
/// (e.g. `Box::<_>::new` separately from determining that a method call
209+
/// is occuring). We use this check to skip over `InferredPaths` for
210+
/// non-method-calls (e.g. struct constructors).
47211
if let ty::FnDef(_, substs) = ty.kind {
48-
debug!("Got substs: {:?}", substs);
212+
debug!("find_questionable_call: Got substs: {:?}", substs);
49213
let mut args_inhabited = true;
50214

215+
// See if we can find any arguments that are definitely uninhabited.
216+
// If we can, we're done - the method call is dead, so fallback
217+
// cannot affect its runtime behavior.
51218
for arg in &**path.args.as_ref().unwrap() {
52219
let resolved_arg = fcx.infcx.resolve_vars_if_possible(arg);
53220

221+
// We use `conservative_is_privately_uninhabited` so that we only
222+
// bail out if we're certain that a type is uninhabited.
54223
if resolved_arg.conservative_is_privately_uninhabited(tcx) {
55-
debug!("post_fallback: Arg is uninhabited: {:?}", resolved_arg);
56-
args_inhabited = false;
57-
break;
224+
debug!("find_questionable_call: bailing out due to uninhabited arg: {:?}", resolved_arg);
225+
return None;
58226
} else {
59-
debug!("post_fallback: Arg is inhabited: {:?}", resolved_arg);
227+
debug!("find_questionable_call: Arg is inhabited: {:?}", resolved_arg);
60228
}
61229
}
62230

63-
if !args_inhabited {
64-
debug!("post_fallback: Not all arguments are inhabited");
65-
return None;
66-
}
67-
231+
// Now, check the generic substs for the method (e.g. `T` and `V` in `foo::<T, V>()`.
232+
// If we find an uninhabited subst,
68233
for (subst_ty, vars) in substs.types().zip(path.unresolved_vars.iter()) {
69234
let resolved_subst = fcx.infcx.resolve_vars_if_possible(&subst_ty);
70-
if resolved_subst.conservative_is_privately_uninhabited(tcx) {
71-
debug!("post_fallback: Subst is uninhabited: {:?}", resolved_subst);
235+
// We use `is_ty_uninhabited_from_any_module` instead of `conservative_is_privately_uninhabited`.
236+
// Using a broader check for uninhabitedness ensures that we lint
237+
// whenever the subst is uninhabted, regardless of whether or not
238+
// the user code could know this.
239+
if tcx.is_ty_uninhabited_from_any_module(resolved_subst){
240+
debug!("find_questionable_call: Subst is uninhabited: {:?}", resolved_subst);
72241
if !vars.is_empty() {
73-
debug!("Found fallback vars: {:?}", vars);
242+
debug!("find_questionable_call: Found fallback vars: {:?}", vars);
74243
debug!(
75-
"post_fallback: All arguments are inhabited, at least one subst is not inhabited!"
244+
"find_questionable_call: All arguments are inhabited, at least one subst is not inhabited!"
76245
);
246+
// These variables were recorded prior to fallback runntime.
247+
// When generating a diagnostic message, we will use these
248+
// to determine which spans will we show to the user.
77249
return Some(vars);
78250
} else {
79-
debug!("No fallback vars")
251+
debug!("find_questionable_call: No fallback vars")
80252
}
81253
} else {
82-
debug!("post_fallback: Subst is inhabited: {:?}", resolved_subst);
254+
debug!("find_questionable_call: Subst is inhabited: {:?}", resolved_subst);
83255
}
84256
}
85257
}
86258
return None;
87259
}
88260

261+
/// Holds the "best" type variables that we want
262+
/// to use to generate a message for the user.
263+
///
264+
/// Note that these two variables are unified with each other,
265+
/// and therefore represent the same type. However, they have different
266+
/// origin information, which we can use to generate a nice error message.
89267
struct VarData {
268+
/// A type variable that was actually used in an uninhabited
269+
/// generic subst (e.g. `foo::<_#0t>()` where `_#0t = !`)
270+
/// This is used to point at the span where the U.B potentially
271+
/// occurs
90272
best_var: TyVid,
273+
/// The diverging type variable that is ultimately responsible
274+
/// for the U.B. occuring. In the `obj` example, this would
275+
/// be the type variable corresponding the `panic!()` expression.
91276
best_diverging_var: TyVid,
92277
}
93278

94279
impl<'tcx> NeverCompatHandler<'tcx> {
280+
/// The pre-fallback hook invoked by typecheck. We collect
281+
/// all unconstrained inference variables used in method call substs,
282+
/// so that we can check if any changes occur due to fallback.
95283
pub fn pre_fallback(fcx: &FnCtxt<'a, 'tcx>) -> NeverCompatHandler<'tcx> {
96284
let unresolved_paths: FxHashMap<HirId, InferredPath<'tcx>> = fcx
97285
.inferred_paths
@@ -103,6 +291,7 @@ impl<'tcx> NeverCompatHandler<'tcx> {
103291

104292
let ty_resolved = fcx.infcx.resolve_vars_if_possible(&path.ty);
105293

294+
// We only care about method calls, not other uses of generic paths.
106295
let fn_substs = match ty_resolved {
107296
Some(ty::TyS { kind: ty::FnDef(_, substs), .. }) => substs,
108297
_ => {
@@ -111,6 +300,8 @@ impl<'tcx> NeverCompatHandler<'tcx> {
111300
}
112301
};
113302

303+
// Any method call with inference variables in its substs
304+
// could potentially be affected by fallback.
114305
if fcx.infcx.unresolved_type_vars(fn_substs).is_some() {
115306
for subst in fn_substs.types() {
116307
let mut finder = TyVarFinder { infcx: &fcx.infcx, vars: vec![] };
@@ -131,6 +322,8 @@ impl<'tcx> NeverCompatHandler<'tcx> {
131322
})
132323
.collect();
133324

325+
// Collect all divering inference variables, so that we
326+
// can later compare them against other inference variables.
134327
let unconstrained_diverging: Vec<_> = fcx
135328
.unsolved_variables()
136329
.iter()
@@ -141,6 +334,18 @@ impl<'tcx> NeverCompatHandler<'tcx> {
141334
NeverCompatHandler { unresolved_paths, unconstrained_diverging }
142335
}
143336

337+
/// Finds the 'best' type variables to use for display to the user,
338+
/// given a list of the type variables originally used in the
339+
/// generic substs for a method.
340+
///
341+
/// We look for a type variable that ended up unified with a diverging
342+
/// inference variable. This represents a place where fallback to a never
343+
/// type ended up affecting a live method call. We then return the
344+
/// inference variable, along with the corresponding divering inference
345+
/// variable that was unified with it
346+
///
347+
/// Note that their be multiple such pairs of inference variables - howver,
348+
/// we only display one to the user, to avoid overwhelming them with information.
144349
fn find_best_vars(&self, fcx: &FnCtxt<'a, 'tcx>, vars: &[Ty<'tcx>]) -> VarData {
145350
for var in vars {
146351
for diverging_var in &self.unconstrained_diverging {
@@ -163,9 +368,12 @@ impl<'tcx> NeverCompatHandler<'tcx> {
163368
}
164369
}
165370
}
166-
bug!("No vars were equated to divering vars: {:?}", vars)
371+
bug!("No vars were equated to diverging vars: {:?}", vars)
167372
}
168373

374+
/// The hook used by typecheck after fallback has been run.
375+
/// We look for any "questionable" calls, generating diagnostic
376+
/// message for them.
169377
pub fn post_fallback(self, fcx: &FnCtxt<'a, 'tcx>) {
170378
let tcx = fcx.tcx;
171379
for (call_id, path) in &self.unresolved_paths {
@@ -190,7 +398,13 @@ impl<'tcx> NeverCompatHandler<'tcx> {
190398
.sess
191399
.struct_span_warn(span, "Fallback to `!` may introduce undefined behavior");
192400

401+
// There are two possible cases here:
193402
match var_origin.kind {
403+
// 1. This inference variable was automatically generated due to the user
404+
// not using the turbofish syntax. For example, `foo()` where foo is deinfed
405+
// as `fn foo<T>() { ... }`.
406+
// In this case, we point at the definition of the type variable (e.g. the `T`
407+
// in `foo<T>`), to better explain to the user what's going on.
194408
TypeVariableOriginKind::TypeParameterDefinition(name, did) => {
195409
err.span_note(
196410
var_origin.span,
@@ -200,6 +414,9 @@ impl<'tcx> NeverCompatHandler<'tcx> {
200414
err.span_note(fcx.tcx.def_span(did), "(type parameter defined here)");
201415
}
202416
}
417+
// The user wrote an explicit inference variable: e.g. `foo::<_>()`. We point
418+
// directly at its span, since this is sufficient to explain to the user
419+
// what's going on.
203420
_ => {
204421
err.span_note(var_origin.span, "the type here was inferred to `!`");
205422
}

0 commit comments

Comments
 (0)