@@ -9,14 +9,162 @@ use rustc_data_structures::fx::FxHashMap;
9
9
use rustc_hir:: HirId ;
10
10
11
11
/// 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;
13
13
///
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.
14
148
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.
15
153
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.
16
157
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.
17
163
unconstrained_diverging : Vec < Ty < ' tcx > > ,
18
164
}
19
165
166
+ /// A simpler helper to collect all `InferTy::TyVar`s found
167
+ /// in a `TypeFoldable`.
20
168
struct TyVarFinder < ' a , ' tcx > {
21
169
infcx : & ' a InferCtxt < ' a , ' tcx > ,
22
170
vars : Vec < Ty < ' tcx > > ,
@@ -34,64 +182,104 @@ impl<'a, 'tcx> TypeFolder<'tcx> for TyVarFinder<'a, 'tcx> {
34
182
}
35
183
}
36
184
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
37
196
fn find_questionable_call < ' a , ' tcx > (
38
197
path : & ' a InferredPath < ' tcx > ,
39
198
fcx : & FnCtxt < ' a , ' tcx > ,
40
199
) -> Option < & ' a [ Ty < ' tcx > ] > {
41
200
let tcx = fcx. tcx ;
42
201
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) ;
44
203
45
204
let ty = ty. unwrap_or_else ( || bug ! ( "Missing ty in path: {:?}" , path) ) ;
46
205
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).
47
211
if let ty:: FnDef ( _, substs) = ty. kind {
48
- debug ! ( "Got substs: {:?}" , substs) ;
212
+ debug ! ( "find_questionable_call: Got substs: {:?}" , substs) ;
49
213
let mut args_inhabited = true ;
50
214
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.
51
218
for arg in & * * path. args . as_ref ( ) . unwrap ( ) {
52
219
let resolved_arg = fcx. infcx . resolve_vars_if_possible ( arg) ;
53
220
221
+ // We use `conservative_is_privately_uninhabited` so that we only
222
+ // bail out if we're certain that a type is uninhabited.
54
223
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 ;
58
226
} else {
59
- debug ! ( "post_fallback : Arg is inhabited: {:?}" , resolved_arg) ;
227
+ debug ! ( "find_questionable_call : Arg is inhabited: {:?}" , resolved_arg) ;
60
228
}
61
229
}
62
230
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,
68
233
for ( subst_ty, vars) in substs. types ( ) . zip ( path. unresolved_vars . iter ( ) ) {
69
234
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) ;
72
241
if !vars. is_empty ( ) {
73
- debug ! ( "Found fallback vars: {:?}" , vars) ;
242
+ debug ! ( "find_questionable_call: Found fallback vars: {:?}" , vars) ;
74
243
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!"
76
245
) ;
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.
77
249
return Some ( vars) ;
78
250
} else {
79
- debug ! ( "No fallback vars" )
251
+ debug ! ( "find_questionable_call: No fallback vars" )
80
252
}
81
253
} else {
82
- debug ! ( "post_fallback : Subst is inhabited: {:?}" , resolved_subst) ;
254
+ debug ! ( "find_questionable_call : Subst is inhabited: {:?}" , resolved_subst) ;
83
255
}
84
256
}
85
257
}
86
258
return None ;
87
259
}
88
260
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.
89
267
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
90
272
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.
91
276
best_diverging_var : TyVid ,
92
277
}
93
278
94
279
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.
95
283
pub fn pre_fallback ( fcx : & FnCtxt < ' a , ' tcx > ) -> NeverCompatHandler < ' tcx > {
96
284
let unresolved_paths: FxHashMap < HirId , InferredPath < ' tcx > > = fcx
97
285
. inferred_paths
@@ -103,6 +291,7 @@ impl<'tcx> NeverCompatHandler<'tcx> {
103
291
104
292
let ty_resolved = fcx. infcx . resolve_vars_if_possible ( & path. ty ) ;
105
293
294
+ // We only care about method calls, not other uses of generic paths.
106
295
let fn_substs = match ty_resolved {
107
296
Some ( ty:: TyS { kind : ty:: FnDef ( _, substs) , .. } ) => substs,
108
297
_ => {
@@ -111,6 +300,8 @@ impl<'tcx> NeverCompatHandler<'tcx> {
111
300
}
112
301
} ;
113
302
303
+ // Any method call with inference variables in its substs
304
+ // could potentially be affected by fallback.
114
305
if fcx. infcx . unresolved_type_vars ( fn_substs) . is_some ( ) {
115
306
for subst in fn_substs. types ( ) {
116
307
let mut finder = TyVarFinder { infcx : & fcx. infcx , vars : vec ! [ ] } ;
@@ -131,6 +322,8 @@ impl<'tcx> NeverCompatHandler<'tcx> {
131
322
} )
132
323
. collect ( ) ;
133
324
325
+ // Collect all divering inference variables, so that we
326
+ // can later compare them against other inference variables.
134
327
let unconstrained_diverging: Vec < _ > = fcx
135
328
. unsolved_variables ( )
136
329
. iter ( )
@@ -141,6 +334,18 @@ impl<'tcx> NeverCompatHandler<'tcx> {
141
334
NeverCompatHandler { unresolved_paths, unconstrained_diverging }
142
335
}
143
336
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.
144
349
fn find_best_vars ( & self , fcx : & FnCtxt < ' a , ' tcx > , vars : & [ Ty < ' tcx > ] ) -> VarData {
145
350
for var in vars {
146
351
for diverging_var in & self . unconstrained_diverging {
@@ -163,9 +368,12 @@ impl<'tcx> NeverCompatHandler<'tcx> {
163
368
}
164
369
}
165
370
}
166
- bug ! ( "No vars were equated to divering vars: {:?}" , vars)
371
+ bug ! ( "No vars were equated to diverging vars: {:?}" , vars)
167
372
}
168
373
374
+ /// The hook used by typecheck after fallback has been run.
375
+ /// We look for any "questionable" calls, generating diagnostic
376
+ /// message for them.
169
377
pub fn post_fallback ( self , fcx : & FnCtxt < ' a , ' tcx > ) {
170
378
let tcx = fcx. tcx ;
171
379
for ( call_id, path) in & self . unresolved_paths {
@@ -190,7 +398,13 @@ impl<'tcx> NeverCompatHandler<'tcx> {
190
398
. sess
191
399
. struct_span_warn ( span, "Fallback to `!` may introduce undefined behavior" ) ;
192
400
401
+ // There are two possible cases here:
193
402
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.
194
408
TypeVariableOriginKind :: TypeParameterDefinition ( name, did) => {
195
409
err. span_note (
196
410
var_origin. span ,
@@ -200,6 +414,9 @@ impl<'tcx> NeverCompatHandler<'tcx> {
200
414
err. span_note ( fcx. tcx . def_span ( did) , "(type parameter defined here)" ) ;
201
415
}
202
416
}
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.
203
420
_ => {
204
421
err. span_note ( var_origin. span , "the type here was inferred to `!`" ) ;
205
422
}
0 commit comments