From 5cbf17290923c04487e031f882846d1320832eff Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 3 Nov 2022 09:10:31 +0000 Subject: [PATCH] Print a trace through types to show how to get to the problematic type --- compiler/rustc_lint/src/builtin.rs | 106 +++++++++++------- .../validate_uninhabited_zsts.32bit.stderr | 5 + .../validate_uninhabited_zsts.64bit.stderr | 5 + src/test/ui/lint/invalid_value.stderr | 49 +++++--- 4 files changed, 110 insertions(+), 55 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index ada3c3b67fb0..700bf4a0aca9 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -57,8 +57,6 @@ use rustc_trait_selection::traits::{self, misc::can_type_implement_copy}; use crate::nonstandard_style::{method_context, MethodLateContext}; -use std::fmt::Write; - // hardwired lints from librustc_middle pub use rustc_session::lint::builtin::*; @@ -2408,8 +2406,34 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { } /// Information about why a type cannot be initialized this way. - /// Contains an error message and optionally a span to point at. - type InitError = (String, Option); + struct InitError { + message: String, + /// Spans from struct fields and similar can be obtained from just the type. + span: Option, + /// Used to report a trace through adts. + nested: Option>, + } + impl InitError { + fn spanned(self, span: Span) -> InitError { + Self { span: Some(span), ..self } + } + + fn nested(self, nested: InitError) -> InitError { + assert!(self.nested.is_none()); + Self { nested: Some(Box::new(nested)), ..self } + } + } + + impl<'a> From<&'a str> for InitError { + fn from(s: &'a str) -> Self { + s.to_owned().into() + } + } + impl From for InitError { + fn from(message: String) -> Self { + Self { message, span: None, nested: None } + } + } /// Test if this constant is all-0. fn is_zero(expr: &hir::Expr<'_>) -> bool { @@ -2471,17 +2495,10 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { init: InitKind, ) -> Option { variant.fields.iter().find_map(|field| { - ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|(mut msg, span)| { - if span.is_none() { - // Point to this field, should be helpful for figuring - // out where the source of the error is. - let span = cx.tcx.def_span(field.did); - write!(&mut msg, " (in this {descr})").unwrap(); - (msg, Some(span)) - } else { - // Just forward. - (msg, span) - } + ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|err| { + InitError::from(format!("in this {descr}")) + .spanned(cx.tcx.def_span(field.did)) + .nested(err) }) }) } @@ -2496,30 +2513,30 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { use rustc_type_ir::sty::TyKind::*; match ty.kind() { // Primitive types that don't like 0 as a value. - Ref(..) => Some(("references must be non-null".to_string(), None)), - Adt(..) if ty.is_box() => Some(("`Box` must be non-null".to_string(), None)), - FnPtr(..) => Some(("function pointers must be non-null".to_string(), None)), - Never => Some(("the `!` type has no valid value".to_string(), None)), + Ref(..) => Some("references must be non-null".into()), + Adt(..) if ty.is_box() => Some("`Box` must be non-null".into()), + FnPtr(..) => Some("function pointers must be non-null".into()), + Never => Some("the `!` type has no valid value".into()), RawPtr(tm) if matches!(tm.ty.kind(), Dynamic(..)) => // raw ptr to dyn Trait { - Some(("the vtable of a wide raw pointer must be non-null".to_string(), None)) + Some("the vtable of a wide raw pointer must be non-null".into()) } // Primitive types with other constraints. Bool if init == InitKind::Uninit => { - Some(("booleans must be either `true` or `false`".to_string(), None)) + Some("booleans must be either `true` or `false`".into()) } Char if init == InitKind::Uninit => { - Some(("characters must be a valid Unicode codepoint".to_string(), None)) + Some("characters must be a valid Unicode codepoint".into()) } Int(_) | Uint(_) if init == InitKind::Uninit => { - Some(("integers must not be uninitialized".to_string(), None)) + Some("integers must not be uninitialized".into()) } Float(_) if init == InitKind::Uninit => { - Some(("floats must not be uninitialized".to_string(), None)) + Some("floats must not be uninitialized".into()) } RawPtr(_) if init == InitKind::Uninit => { - Some(("raw pointers must not be uninitialized".to_string(), None)) + Some("raw pointers must not be uninitialized".into()) } // Recurse and checks for some compound types. (but not unions) Adt(adt_def, substs) if !adt_def.is_union() => { @@ -2531,21 +2548,21 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // handle the attribute correctly.) // We don't add a span since users cannot declare such types anyway. (Bound::Included(lo), Bound::Included(hi)) if 0 < lo && lo < hi => { - return Some((format!("`{}` must be non-null", ty), None)); + return Some(format!("`{}` must be non-null", ty).into()); } (Bound::Included(lo), Bound::Unbounded) if 0 < lo => { - return Some((format!("`{}` must be non-null", ty), None)); + return Some(format!("`{}` must be non-null", ty).into()); } (Bound::Included(_), _) | (_, Bound::Included(_)) if init == InitKind::Uninit => { - return Some(( + return Some( format!( "`{}` must be initialized inside its custom valid range", ty, - ), - None, - )); + ) + .into(), + ); } _ => {} } @@ -2576,7 +2593,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { Some((variant, definitely_inhabited)) }); let Some(first_variant) = potential_variants.next() else { - return Some(("enums with no inhabited variants have no valid value".to_string(), Some(span))); + return Some(InitError::from("enums with no inhabited variants have no valid value").spanned(span)); }; // So we have at least one potentially inhabited variant. Might we have two? let Some(second_variant) = potential_variants.next() else { @@ -2600,10 +2617,9 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { .filter(|(_variant, definitely_inhabited)| *definitely_inhabited) .count(); if definitely_inhabited > 1 { - return Some(( - "enums with multiple inhabited variants have to be initialized to a variant".to_string(), - Some(span), - )); + return Some(InitError::from( + "enums with multiple inhabited variants have to be initialized to a variant", + ).spanned(span)); } } // We couldn't find anything wrong here. @@ -2632,8 +2648,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { // using zeroed or uninitialized memory. // We are extremely conservative with what we warn about. let conjured_ty = cx.typeck_results().expr_ty(expr); - if let Some((msg, span)) = - with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) + if let Some(mut err) = with_no_trimmed_paths!(ty_find_init_error(cx, conjured_ty, init)) { // FIXME(davidtwco): make translatable cx.struct_span_lint( @@ -2659,10 +2674,17 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue { "help: use `MaybeUninit` instead, \ and only call `assume_init` after initialization is done", ); - if let Some(span) = span { - lint.span_note(span, &msg); - } else { - lint.note(&msg); + loop { + if let Some(span) = err.span { + lint.span_note(span, &err.message); + } else { + lint.note(&err.message); + } + if let Some(e) = err.nested { + err = *e; + } else { + break; + } } lint }, diff --git a/src/test/ui/consts/const-eval/validate_uninhabited_zsts.32bit.stderr b/src/test/ui/consts/const-eval/validate_uninhabited_zsts.32bit.stderr index 63639729a2a9..8b4d845b30ea 100644 --- a/src/test/ui/consts/const-eval/validate_uninhabited_zsts.32bit.stderr +++ b/src/test/ui/consts/const-eval/validate_uninhabited_zsts.32bit.stderr @@ -40,6 +40,11 @@ LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3]; | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | +note: in this struct field + --> $DIR/validate_uninhabited_zsts.rs:16:22 + | +LL | pub struct Empty(Void); + | ^^^^ note: enums with no inhabited variants have no valid value --> $DIR/validate_uninhabited_zsts.rs:13:5 | diff --git a/src/test/ui/consts/const-eval/validate_uninhabited_zsts.64bit.stderr b/src/test/ui/consts/const-eval/validate_uninhabited_zsts.64bit.stderr index 63639729a2a9..8b4d845b30ea 100644 --- a/src/test/ui/consts/const-eval/validate_uninhabited_zsts.64bit.stderr +++ b/src/test/ui/consts/const-eval/validate_uninhabited_zsts.64bit.stderr @@ -40,6 +40,11 @@ LL | const BAR: [empty::Empty; 3] = [unsafe { std::mem::transmute(()) }; 3]; | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | +note: in this struct field + --> $DIR/validate_uninhabited_zsts.rs:16:22 + | +LL | pub struct Empty(Void); + | ^^^^ note: enums with no inhabited variants have no valid value --> $DIR/validate_uninhabited_zsts.rs:13:5 | diff --git a/src/test/ui/lint/invalid_value.stderr b/src/test/ui/lint/invalid_value.stderr index 76afb765f0f0..7b452325ccb4 100644 --- a/src/test/ui/lint/invalid_value.stderr +++ b/src/test/ui/lint/invalid_value.stderr @@ -34,11 +34,12 @@ LL | let _val: Wrap<&'static T> = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:17:18 | LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ + = note: references must be non-null error: the type `Wrap<&T>` does not permit being left uninitialized --> $DIR/invalid_value.rs:58:38 @@ -49,11 +50,12 @@ LL | let _val: Wrap<&'static T> = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:17:18 | LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ + = note: references must be non-null error: the type `!` does not permit zero-initialization --> $DIR/invalid_value.rs:65:23 @@ -160,11 +162,12 @@ LL | let _val: Ref = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:14:12 | LL | struct Ref(&'static i32); | ^^^^^^^^^^^^ + = note: references must be non-null error: the type `Ref` does not permit being left uninitialized --> $DIR/invalid_value.rs:78:25 @@ -175,11 +178,12 @@ LL | let _val: Ref = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:14:12 | LL | struct Ref(&'static i32); | ^^^^^^^^^^^^ + = note: references must be non-null error: the type `fn()` does not permit zero-initialization --> $DIR/invalid_value.rs:80:26 @@ -212,11 +216,12 @@ LL | let _val: Wrap = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: function pointers must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:17:18 | LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ + = note: function pointers must be non-null error: the type `Wrap` does not permit being left uninitialized --> $DIR/invalid_value.rs:84:32 @@ -227,11 +232,12 @@ LL | let _val: Wrap = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: function pointers must be non-null (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:17:18 | LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ + = note: function pointers must be non-null error: the type `WrapEnum` does not permit zero-initialization --> $DIR/invalid_value.rs:86:36 @@ -242,11 +248,12 @@ LL | let _val: WrapEnum = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: function pointers must be non-null (in this field of the only potentially inhabited enum variant) +note: in this field of the only potentially inhabited enum variant --> $DIR/invalid_value.rs:18:28 | LL | enum WrapEnum { Wrapped(T) } | ^ + = note: function pointers must be non-null error: the type `WrapEnum` does not permit being left uninitialized --> $DIR/invalid_value.rs:87:36 @@ -257,11 +264,12 @@ LL | let _val: WrapEnum = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: function pointers must be non-null (in this field of the only potentially inhabited enum variant) +note: in this field of the only potentially inhabited enum variant --> $DIR/invalid_value.rs:18:28 | LL | enum WrapEnum { Wrapped(T) } | ^ + = note: function pointers must be non-null error: the type `Wrap<(RefPair, i32)>` does not permit zero-initialization --> $DIR/invalid_value.rs:89:42 @@ -272,11 +280,17 @@ LL | let _val: Wrap<(RefPair, i32)> = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field + --> $DIR/invalid_value.rs:17:18 + | +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ +note: in this struct field --> $DIR/invalid_value.rs:15:16 | LL | struct RefPair((&'static i32, i32)); | ^^^^^^^^^^^^^^^^^^^ + = note: references must be non-null error: the type `Wrap<(RefPair, i32)>` does not permit being left uninitialized --> $DIR/invalid_value.rs:90:42 @@ -287,11 +301,17 @@ LL | let _val: Wrap<(RefPair, i32)> = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: references must be non-null (in this struct field) +note: in this struct field + --> $DIR/invalid_value.rs:17:18 + | +LL | struct Wrap { wrapped: T } + | ^^^^^^^^^^ +note: in this struct field --> $DIR/invalid_value.rs:15:16 | LL | struct RefPair((&'static i32, i32)); | ^^^^^^^^^^^^^^^^^^^ + = note: references must be non-null error: the type `NonNull` does not permit zero-initialization --> $DIR/invalid_value.rs:92:34 @@ -420,11 +440,12 @@ LL | let _val: OneFruitNonZero = mem::zeroed(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: `std::num::NonZeroU32` must be non-null (in this field of the only potentially inhabited enum variant) +note: in this field of the only potentially inhabited enum variant --> $DIR/invalid_value.rs:39:12 | LL | Banana(NonZeroU32), | ^^^^^^^^^^ + = note: `std::num::NonZeroU32` must be non-null error: the type `OneFruitNonZero` does not permit being left uninitialized --> $DIR/invalid_value.rs:108:37 @@ -435,11 +456,12 @@ LL | let _val: OneFruitNonZero = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: `std::num::NonZeroU32` must be non-null (in this field of the only potentially inhabited enum variant) +note: in this field of the only potentially inhabited enum variant --> $DIR/invalid_value.rs:39:12 | LL | Banana(NonZeroU32), | ^^^^^^^^^^ + = note: `std::num::NonZeroU32` must be non-null error: the type `bool` does not permit being left uninitialized --> $DIR/invalid_value.rs:112:26 @@ -461,11 +483,12 @@ LL | let _val: Wrap = mem::uninitialized(); | this code causes undefined behavior when executed | help: use `MaybeUninit` instead, and only call `assume_init` after initialization is done | -note: characters must be a valid Unicode codepoint (in this struct field) +note: in this struct field --> $DIR/invalid_value.rs:17:18 | LL | struct Wrap { wrapped: T } | ^^^^^^^^^^ + = note: characters must be a valid Unicode codepoint error: the type `NonBig` does not permit being left uninitialized --> $DIR/invalid_value.rs:118:28