@@ -9,14 +9,162 @@ use rustc_data_structures::fx::FxHashMap;
99use 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.
15153pub 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`.
20168struct 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
37196fn 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.
89267struct 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
94279impl < ' 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