Skip to content

Commit 60960a2

Browse files
committedAug 12, 2019
Auto merge of #63483 - RalfJung:invalid-value, r=Centril
Improve invalid_value lint message The lint now explains which type is involved and why it cannot be initialized this way. It also points at the innermost struct/enum field that has an offending type, if any. See AltF02/x11-rs#99 (comment) for how this helps in some real-world code hitting this lint.
2 parents c01be67 + 0499923 commit 60960a2

File tree

3 files changed

+224
-74
lines changed

3 files changed

+224
-74
lines changed
 

‎src/librustc_lint/builtin.rs

+41-19
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
//! If you define a new `LateLintPass`, you will also need to add it to the
2222
//! `late_lint_methods!` invocation in `lib.rs`.
2323
24+
use std::fmt::Write;
25+
2426
use rustc::hir::def::{Res, DefKind};
2527
use rustc::hir::def_id::{DefId, LOCAL_CRATE};
2628
use rustc::ty::{self, Ty, TyCtxt, layout::VariantIdx};
@@ -1877,41 +1879,57 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
18771879
const ZEROED_PATH: &[Symbol] = &[sym::core, sym::mem, sym::zeroed];
18781880
const UININIT_PATH: &[Symbol] = &[sym::core, sym::mem, sym::uninitialized];
18791881

1880-
/// Return `false` only if we are sure this type does *not*
1882+
/// Information about why a type cannot be initialized this way.
1883+
/// Contains an error message and optionally a span to point at.
1884+
type InitError = (String, Option<Span>);
1885+
1886+
/// Return `Some` only if we are sure this type does *not*
18811887
/// allow zero initialization.
1882-
fn ty_maybe_allows_zero_init<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
1888+
fn ty_find_init_error<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<InitError> {
18831889
use rustc::ty::TyKind::*;
18841890
match ty.sty {
18851891
// Primitive types that don't like 0 as a value.
1886-
Ref(..) | FnPtr(..) | Never => false,
1887-
Adt(..) if ty.is_box() => false,
1892+
Ref(..) => Some((format!("References must be non-null"), None)),
1893+
Adt(..) if ty.is_box() => Some((format!("`Box` must be non-null"), None)),
1894+
FnPtr(..) => Some((format!("Function pointers must be non-null"), None)),
1895+
Never => Some((format!("The never type (`!`) has no valid value"), None)),
18881896
// Recurse for some compound types.
18891897
Adt(adt_def, substs) if !adt_def.is_union() => {
18901898
match adt_def.variants.len() {
1891-
0 => false, // Uninhabited enum!
1899+
0 => Some((format!("0-variant enums have no valid value"), None)),
18921900
1 => {
18931901
// Struct, or enum with exactly one variant.
18941902
// Proceed recursively, check all fields.
18951903
let variant = &adt_def.variants[VariantIdx::from_u32(0)];
1896-
variant.fields.iter().all(|field| {
1897-
ty_maybe_allows_zero_init(
1904+
variant.fields.iter().find_map(|field| {
1905+
ty_find_init_error(
18981906
tcx,
18991907
field.ty(tcx, substs),
1900-
)
1908+
).map(|(mut msg, span)| if span.is_none() {
1909+
// Point to this field, should be helpful for figuring
1910+
// out where the source of the error is.
1911+
let span = tcx.def_span(field.did);
1912+
write!(&mut msg, " (in this {} field)", adt_def.descr())
1913+
.unwrap();
1914+
(msg, Some(span))
1915+
} else {
1916+
// Just forward.
1917+
(msg, span)
1918+
})
19011919
})
19021920
}
1903-
_ => true, // Conservative fallback for multi-variant enum.
1921+
_ => None, // Conservative fallback for multi-variant enum.
19041922
}
19051923
}
19061924
Tuple(..) => {
19071925
// Proceed recursively, check all fields.
1908-
ty.tuple_fields().all(|field| ty_maybe_allows_zero_init(tcx, field))
1926+
ty.tuple_fields().find_map(|field| ty_find_init_error(tcx, field))
19091927
}
19101928
// FIXME: Would be nice to also warn for `NonNull`/`NonZero*`.
19111929
// FIXME: *Only for `mem::uninitialized`*, we could also warn for `bool`,
19121930
// `char`, and any multivariant enum.
19131931
// Conservative fallback.
1914-
_ => true,
1932+
_ => None,
19151933
}
19161934
}
19171935

@@ -1925,9 +1943,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19251943
// using zeroed or uninitialized memory.
19261944
// We are extremely conservative with what we warn about.
19271945
let conjured_ty = cx.tables.expr_ty(expr);
1928-
1929-
if !ty_maybe_allows_zero_init(cx.tcx, conjured_ty) {
1930-
cx.struct_span_lint(
1946+
if let Some((msg, span)) = ty_find_init_error(cx.tcx, conjured_ty) {
1947+
let mut err = cx.struct_span_lint(
19311948
INVALID_VALUE,
19321949
expr.span,
19331950
&format!(
@@ -1939,11 +1956,16 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for InvalidValue {
19391956
"being left uninitialized"
19401957
}
19411958
),
1942-
)
1943-
.note("this means that this code causes undefined behavior \
1944-
when executed")
1945-
.help("use `MaybeUninit` instead")
1946-
.emit();
1959+
);
1960+
err.span_label(expr.span,
1961+
"this code causes undefined behavior when executed");
1962+
err.span_label(expr.span, "help: use `MaybeUninit<T>` instead");
1963+
if let Some(span) = span {
1964+
err.span_note(span, &msg);
1965+
} else {
1966+
err.note(&msg);
1967+
}
1968+
err.emit();
19471969
}
19481970
}
19491971
}

‎src/test/ui/lint/uninitialized-zeroed.rs

+8
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ use std::mem::{self, MaybeUninit};
1111
enum Void {}
1212

1313
struct Ref(&'static i32);
14+
struct RefPair((&'static i32, i32));
1415

1516
struct Wrap<T> { wrapped: T }
17+
enum WrapEnum<T> { Wrapped(T) }
1618

1719
#[allow(unused)]
1820
fn generic<T: 'static>() {
@@ -48,6 +50,12 @@ fn main() {
4850
let _val: Wrap<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
4951
let _val: Wrap<fn()> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
5052

53+
let _val: WrapEnum<fn()> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
54+
let _val: WrapEnum<fn()> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
55+
56+
let _val: Wrap<(RefPair, i32)> = mem::zeroed(); //~ ERROR: does not permit zero-initialization
57+
let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); //~ ERROR: does not permit being left uninitialized
58+
5159
// Some types that should work just fine.
5260
let _val: Option<&'static i32> = mem::zeroed();
5361
let _val: Option<fn()> = mem::zeroed();
+175-55
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,289 @@
11
error: the type `&'static T` does not permit zero-initialization
2-
--> $DIR/uninitialized-zeroed.rs:20:32
2+
--> $DIR/uninitialized-zeroed.rs:22:32
33
|
44
LL | let _val: &'static T = mem::zeroed();
55
| ^^^^^^^^^^^^^
6+
| |
7+
| this code causes undefined behavior when executed
8+
| help: use `MaybeUninit<T>` instead
69
|
710
note: lint level defined here
811
--> $DIR/uninitialized-zeroed.rs:7:9
912
|
1013
LL | #![deny(invalid_value)]
1114
| ^^^^^^^^^^^^^
12-
= note: this means that this code causes undefined behavior when executed
13-
= help: use `MaybeUninit` instead
15+
= note: References must be non-null
1416

1517
error: the type `&'static T` does not permit being left uninitialized
16-
--> $DIR/uninitialized-zeroed.rs:21:32
18+
--> $DIR/uninitialized-zeroed.rs:23:32
1719
|
1820
LL | let _val: &'static T = mem::uninitialized();
1921
| ^^^^^^^^^^^^^^^^^^^^
22+
| |
23+
| this code causes undefined behavior when executed
24+
| help: use `MaybeUninit<T>` instead
2025
|
21-
= note: this means that this code causes undefined behavior when executed
22-
= help: use `MaybeUninit` instead
26+
= note: References must be non-null
2327

2428
error: the type `Wrap<&'static T>` does not permit zero-initialization
25-
--> $DIR/uninitialized-zeroed.rs:23:38
29+
--> $DIR/uninitialized-zeroed.rs:25:38
2630
|
2731
LL | let _val: Wrap<&'static T> = mem::zeroed();
2832
| ^^^^^^^^^^^^^
33+
| |
34+
| this code causes undefined behavior when executed
35+
| help: use `MaybeUninit<T>` instead
2936
|
30-
= note: this means that this code causes undefined behavior when executed
31-
= help: use `MaybeUninit` instead
37+
note: References must be non-null (in this struct field)
38+
--> $DIR/uninitialized-zeroed.rs:16:18
39+
|
40+
LL | struct Wrap<T> { wrapped: T }
41+
| ^^^^^^^^^^
3242

3343
error: the type `Wrap<&'static T>` does not permit being left uninitialized
34-
--> $DIR/uninitialized-zeroed.rs:24:38
44+
--> $DIR/uninitialized-zeroed.rs:26:38
3545
|
3646
LL | let _val: Wrap<&'static T> = mem::uninitialized();
3747
| ^^^^^^^^^^^^^^^^^^^^
48+
| |
49+
| this code causes undefined behavior when executed
50+
| help: use `MaybeUninit<T>` instead
51+
|
52+
note: References must be non-null (in this struct field)
53+
--> $DIR/uninitialized-zeroed.rs:16:18
3854
|
39-
= note: this means that this code causes undefined behavior when executed
40-
= help: use `MaybeUninit` instead
55+
LL | struct Wrap<T> { wrapped: T }
56+
| ^^^^^^^^^^
4157

4258
error: the type `!` does not permit zero-initialization
43-
--> $DIR/uninitialized-zeroed.rs:30:23
59+
--> $DIR/uninitialized-zeroed.rs:32:23
4460
|
4561
LL | let _val: ! = mem::zeroed();
4662
| ^^^^^^^^^^^^^
63+
| |
64+
| this code causes undefined behavior when executed
65+
| help: use `MaybeUninit<T>` instead
4766
|
48-
= note: this means that this code causes undefined behavior when executed
49-
= help: use `MaybeUninit` instead
67+
= note: The never type (`!`) has no valid value
5068

5169
error: the type `!` does not permit being left uninitialized
52-
--> $DIR/uninitialized-zeroed.rs:31:23
70+
--> $DIR/uninitialized-zeroed.rs:33:23
5371
|
5472
LL | let _val: ! = mem::uninitialized();
5573
| ^^^^^^^^^^^^^^^^^^^^
74+
| |
75+
| this code causes undefined behavior when executed
76+
| help: use `MaybeUninit<T>` instead
5677
|
57-
= note: this means that this code causes undefined behavior when executed
58-
= help: use `MaybeUninit` instead
78+
= note: The never type (`!`) has no valid value
5979

6080
error: the type `(i32, !)` does not permit zero-initialization
61-
--> $DIR/uninitialized-zeroed.rs:33:30
81+
--> $DIR/uninitialized-zeroed.rs:35:30
6282
|
6383
LL | let _val: (i32, !) = mem::zeroed();
6484
| ^^^^^^^^^^^^^
85+
| |
86+
| this code causes undefined behavior when executed
87+
| help: use `MaybeUninit<T>` instead
6588
|
66-
= note: this means that this code causes undefined behavior when executed
67-
= help: use `MaybeUninit` instead
89+
= note: The never type (`!`) has no valid value
6890

6991
error: the type `(i32, !)` does not permit being left uninitialized
70-
--> $DIR/uninitialized-zeroed.rs:34:30
92+
--> $DIR/uninitialized-zeroed.rs:36:30
7193
|
7294
LL | let _val: (i32, !) = mem::uninitialized();
7395
| ^^^^^^^^^^^^^^^^^^^^
96+
| |
97+
| this code causes undefined behavior when executed
98+
| help: use `MaybeUninit<T>` instead
7499
|
75-
= note: this means that this code causes undefined behavior when executed
76-
= help: use `MaybeUninit` instead
100+
= note: The never type (`!`) has no valid value
77101

78102
error: the type `Void` does not permit zero-initialization
79-
--> $DIR/uninitialized-zeroed.rs:36:26
103+
--> $DIR/uninitialized-zeroed.rs:38:26
80104
|
81105
LL | let _val: Void = mem::zeroed();
82106
| ^^^^^^^^^^^^^
107+
| |
108+
| this code causes undefined behavior when executed
109+
| help: use `MaybeUninit<T>` instead
83110
|
84-
= note: this means that this code causes undefined behavior when executed
85-
= help: use `MaybeUninit` instead
111+
= note: 0-variant enums have no valid value
86112

87113
error: the type `Void` does not permit being left uninitialized
88-
--> $DIR/uninitialized-zeroed.rs:37:26
114+
--> $DIR/uninitialized-zeroed.rs:39:26
89115
|
90116
LL | let _val: Void = mem::uninitialized();
91117
| ^^^^^^^^^^^^^^^^^^^^
118+
| |
119+
| this code causes undefined behavior when executed
120+
| help: use `MaybeUninit<T>` instead
92121
|
93-
= note: this means that this code causes undefined behavior when executed
94-
= help: use `MaybeUninit` instead
122+
= note: 0-variant enums have no valid value
95123

96124
error: the type `&'static i32` does not permit zero-initialization
97-
--> $DIR/uninitialized-zeroed.rs:39:34
125+
--> $DIR/uninitialized-zeroed.rs:41:34
98126
|
99127
LL | let _val: &'static i32 = mem::zeroed();
100128
| ^^^^^^^^^^^^^
129+
| |
130+
| this code causes undefined behavior when executed
131+
| help: use `MaybeUninit<T>` instead
101132
|
102-
= note: this means that this code causes undefined behavior when executed
103-
= help: use `MaybeUninit` instead
133+
= note: References must be non-null
104134

105135
error: the type `&'static i32` does not permit being left uninitialized
106-
--> $DIR/uninitialized-zeroed.rs:40:34
136+
--> $DIR/uninitialized-zeroed.rs:42:34
107137
|
108138
LL | let _val: &'static i32 = mem::uninitialized();
109139
| ^^^^^^^^^^^^^^^^^^^^
140+
| |
141+
| this code causes undefined behavior when executed
142+
| help: use `MaybeUninit<T>` instead
110143
|
111-
= note: this means that this code causes undefined behavior when executed
112-
= help: use `MaybeUninit` instead
144+
= note: References must be non-null
113145

114146
error: the type `Ref` does not permit zero-initialization
115-
--> $DIR/uninitialized-zeroed.rs:42:25
147+
--> $DIR/uninitialized-zeroed.rs:44:25
116148
|
117149
LL | let _val: Ref = mem::zeroed();
118150
| ^^^^^^^^^^^^^
151+
| |
152+
| this code causes undefined behavior when executed
153+
| help: use `MaybeUninit<T>` instead
154+
|
155+
note: References must be non-null (in this struct field)
156+
--> $DIR/uninitialized-zeroed.rs:13:12
119157
|
120-
= note: this means that this code causes undefined behavior when executed
121-
= help: use `MaybeUninit` instead
158+
LL | struct Ref(&'static i32);
159+
| ^^^^^^^^^^^^
122160

123161
error: the type `Ref` does not permit being left uninitialized
124-
--> $DIR/uninitialized-zeroed.rs:43:25
162+
--> $DIR/uninitialized-zeroed.rs:45:25
125163
|
126164
LL | let _val: Ref = mem::uninitialized();
127165
| ^^^^^^^^^^^^^^^^^^^^
166+
| |
167+
| this code causes undefined behavior when executed
168+
| help: use `MaybeUninit<T>` instead
128169
|
129-
= note: this means that this code causes undefined behavior when executed
130-
= help: use `MaybeUninit` instead
170+
note: References must be non-null (in this struct field)
171+
--> $DIR/uninitialized-zeroed.rs:13:12
172+
|
173+
LL | struct Ref(&'static i32);
174+
| ^^^^^^^^^^^^
131175

132176
error: the type `fn()` does not permit zero-initialization
133-
--> $DIR/uninitialized-zeroed.rs:45:26
177+
--> $DIR/uninitialized-zeroed.rs:47:26
134178
|
135179
LL | let _val: fn() = mem::zeroed();
136180
| ^^^^^^^^^^^^^
181+
| |
182+
| this code causes undefined behavior when executed
183+
| help: use `MaybeUninit<T>` instead
137184
|
138-
= note: this means that this code causes undefined behavior when executed
139-
= help: use `MaybeUninit` instead
185+
= note: Function pointers must be non-null
140186

141187
error: the type `fn()` does not permit being left uninitialized
142-
--> $DIR/uninitialized-zeroed.rs:46:26
188+
--> $DIR/uninitialized-zeroed.rs:48:26
143189
|
144190
LL | let _val: fn() = mem::uninitialized();
145191
| ^^^^^^^^^^^^^^^^^^^^
192+
| |
193+
| this code causes undefined behavior when executed
194+
| help: use `MaybeUninit<T>` instead
146195
|
147-
= note: this means that this code causes undefined behavior when executed
148-
= help: use `MaybeUninit` instead
196+
= note: Function pointers must be non-null
149197

150198
error: the type `Wrap<fn()>` does not permit zero-initialization
151-
--> $DIR/uninitialized-zeroed.rs:48:32
199+
--> $DIR/uninitialized-zeroed.rs:50:32
152200
|
153201
LL | let _val: Wrap<fn()> = mem::zeroed();
154202
| ^^^^^^^^^^^^^
203+
| |
204+
| this code causes undefined behavior when executed
205+
| help: use `MaybeUninit<T>` instead
206+
|
207+
note: Function pointers must be non-null (in this struct field)
208+
--> $DIR/uninitialized-zeroed.rs:16:18
155209
|
156-
= note: this means that this code causes undefined behavior when executed
157-
= help: use `MaybeUninit` instead
210+
LL | struct Wrap<T> { wrapped: T }
211+
| ^^^^^^^^^^
158212

159213
error: the type `Wrap<fn()>` does not permit being left uninitialized
160-
--> $DIR/uninitialized-zeroed.rs:49:32
214+
--> $DIR/uninitialized-zeroed.rs:51:32
161215
|
162216
LL | let _val: Wrap<fn()> = mem::uninitialized();
163217
| ^^^^^^^^^^^^^^^^^^^^
218+
| |
219+
| this code causes undefined behavior when executed
220+
| help: use `MaybeUninit<T>` instead
221+
|
222+
note: Function pointers must be non-null (in this struct field)
223+
--> $DIR/uninitialized-zeroed.rs:16:18
224+
|
225+
LL | struct Wrap<T> { wrapped: T }
226+
| ^^^^^^^^^^
227+
228+
error: the type `WrapEnum<fn()>` does not permit zero-initialization
229+
--> $DIR/uninitialized-zeroed.rs:53:36
230+
|
231+
LL | let _val: WrapEnum<fn()> = mem::zeroed();
232+
| ^^^^^^^^^^^^^
233+
| |
234+
| this code causes undefined behavior when executed
235+
| help: use `MaybeUninit<T>` instead
236+
|
237+
note: Function pointers must be non-null (in this enum field)
238+
--> $DIR/uninitialized-zeroed.rs:17:28
239+
|
240+
LL | enum WrapEnum<T> { Wrapped(T) }
241+
| ^
242+
243+
error: the type `WrapEnum<fn()>` does not permit being left uninitialized
244+
--> $DIR/uninitialized-zeroed.rs:54:36
245+
|
246+
LL | let _val: WrapEnum<fn()> = mem::uninitialized();
247+
| ^^^^^^^^^^^^^^^^^^^^
248+
| |
249+
| this code causes undefined behavior when executed
250+
| help: use `MaybeUninit<T>` instead
251+
|
252+
note: Function pointers must be non-null (in this enum field)
253+
--> $DIR/uninitialized-zeroed.rs:17:28
254+
|
255+
LL | enum WrapEnum<T> { Wrapped(T) }
256+
| ^
257+
258+
error: the type `Wrap<(RefPair, i32)>` does not permit zero-initialization
259+
--> $DIR/uninitialized-zeroed.rs:56:42
260+
|
261+
LL | let _val: Wrap<(RefPair, i32)> = mem::zeroed();
262+
| ^^^^^^^^^^^^^
263+
| |
264+
| this code causes undefined behavior when executed
265+
| help: use `MaybeUninit<T>` instead
266+
|
267+
note: References must be non-null (in this struct field)
268+
--> $DIR/uninitialized-zeroed.rs:14:16
269+
|
270+
LL | struct RefPair((&'static i32, i32));
271+
| ^^^^^^^^^^^^^^^^^^^
272+
273+
error: the type `Wrap<(RefPair, i32)>` does not permit being left uninitialized
274+
--> $DIR/uninitialized-zeroed.rs:57:42
275+
|
276+
LL | let _val: Wrap<(RefPair, i32)> = mem::uninitialized();
277+
| ^^^^^^^^^^^^^^^^^^^^
278+
| |
279+
| this code causes undefined behavior when executed
280+
| help: use `MaybeUninit<T>` instead
281+
|
282+
note: References must be non-null (in this struct field)
283+
--> $DIR/uninitialized-zeroed.rs:14:16
164284
|
165-
= note: this means that this code causes undefined behavior when executed
166-
= help: use `MaybeUninit` instead
285+
LL | struct RefPair((&'static i32, i32));
286+
| ^^^^^^^^^^^^^^^^^^^
167287

168-
error: aborting due to 18 previous errors
288+
error: aborting due to 22 previous errors
169289

0 commit comments

Comments
 (0)
Please sign in to comment.