From 313714331ac3fbc63e767fe95d175f293cf5d875 Mon Sep 17 00:00:00 2001 From: Gurinder Singh Date: Thu, 4 Apr 2024 15:50:36 +0530 Subject: [PATCH] Error out of layout calculation if a non-last struct field is unsized Fixes an ICE that occurs when a struct with an unsized field at a non-last position is const evaluated. --- compiler/rustc_ty_utils/src/layout.rs | 38 ++++++- ...ice-non-last-unsized-field-issue-121473.rs | 79 +++++++++++++ ...non-last-unsized-field-issue-121473.stderr | 106 ++++++++++++++++++ 3 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 tests/ui/layout/ice-non-last-unsized-field-issue-121473.rs create mode 100644 tests/ui/layout/ice-non-last-unsized-field-issue-121473.stderr diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index 331970ac36233..44390c1624d98 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -8,7 +8,9 @@ use rustc_middle::ty::layout::{ IntegerExt, LayoutCx, LayoutError, LayoutOf, TyAndLayout, MAX_SIMD_LANES, }; use rustc_middle::ty::print::with_no_trimmed_paths; -use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{ + self, AdtDef, EarlyBinder, FieldDef, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt, +}; use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use rustc_span::sym; use rustc_span::symbol::Symbol; @@ -506,6 +508,40 @@ fn layout_of_uncached<'tcx>( )); } + let err_if_unsized = |field: &FieldDef, err_msg: &str| { + let field_ty = tcx.type_of(field.did); + let is_unsized = tcx + .try_instantiate_and_normalize_erasing_regions(args, cx.param_env, field_ty) + .map(|f| !f.is_sized(tcx, cx.param_env)) + .map_err(|e| { + error( + cx, + LayoutError::NormalizationFailure(field_ty.instantiate_identity(), e), + ) + })?; + + if is_unsized { + cx.tcx.dcx().span_delayed_bug(tcx.def_span(def.did()), err_msg.to_owned()); + Err(error(cx, LayoutError::Unknown(ty))) + } else { + Ok(()) + } + }; + + if def.is_struct() { + if let Some((_, fields_except_last)) = + def.non_enum_variant().fields.raw.split_last() + { + for f in fields_except_last { + err_if_unsized(f, "only the last field of a struct can be unsized")?; + } + } + } else { + for f in def.all_fields() { + err_if_unsized(f, &format!("{}s cannot have unsized fields", def.descr()))?; + } + } + let get_discriminant_type = |min, max| Integer::repr_discr(tcx, ty, &def.repr(), min, max); diff --git a/tests/ui/layout/ice-non-last-unsized-field-issue-121473.rs b/tests/ui/layout/ice-non-last-unsized-field-issue-121473.rs new file mode 100644 index 0000000000000..737a7ffbc296c --- /dev/null +++ b/tests/ui/layout/ice-non-last-unsized-field-issue-121473.rs @@ -0,0 +1,79 @@ +// Regression test for #121473 +// Checks that no ICE occurs when `size_of` +// is applied to a struct that has an unsized +// field which is not its last field + +use std::mem::size_of; + +pub struct BadStruct { + pub field1: i32, + pub field2: str, // Unsized field that is not the last field + //~^ ERROR the size for values of type `str` cannot be known at compilation time + pub field3: [u8; 16], +} + +enum BadEnum1 { + Variant1 { + field1: i32, + field2: str, // Unsized + //~^ ERROR the size for values of type `str` cannot be known at compilation time + field3: [u8; 16], + }, +} + +enum BadEnum2 { + Variant1( + i32, + str, // Unsized + //~^ ERROR the size for values of type `str` cannot be known at compilation time + [u8; 16] + ), +} + +enum BadEnumMultiVariant { + Variant1(i32), + Variant2 { + field1: i32, + field2: str, // Unsized + //~^ ERROR the size for values of type `str` cannot be known at compilation time + field3: [u8; 16], + }, + Variant3 +} + +union BadUnion { + field1: i32, + field2: str, // Unsized + //~^ ERROR the size for values of type `str` cannot be known at compilation time + //~| ERROR field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union + field3: [u8; 16], +} + +// Used to test that projection type fields that normalize +// to a sized type do not cause problems +struct StructWithProjections<'a> +{ + field1: <&'a [i32] as IntoIterator>::IntoIter, + field2: i32 +} + +pub fn main() { + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + + let _a = &size_of::(); + assert_eq!(size_of::(), 21); + let _a = StructWithProjections { field1: [1, 3].iter(), field2: 3 }; +} diff --git a/tests/ui/layout/ice-non-last-unsized-field-issue-121473.stderr b/tests/ui/layout/ice-non-last-unsized-field-issue-121473.stderr new file mode 100644 index 0000000000000..626be7ac2836b --- /dev/null +++ b/tests/ui/layout/ice-non-last-unsized-field-issue-121473.stderr @@ -0,0 +1,106 @@ +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:10:17 + | +LL | pub field2: str, // Unsized field that is not the last field + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `str` + = note: only the last field of a struct may have a dynamically sized type + = help: change the field's type to have a statically known size +help: borrowed types always have a statically known size + | +LL | pub field2: &str, // Unsized field that is not the last field + | + +help: the `Box` type always has a statically known size and allocates its contents in the heap + | +LL | pub field2: Box, // Unsized field that is not the last field + | ++++ + + +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:18:17 + | +LL | field2: str, // Unsized + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `str` + = note: no field of an enum variant may have a dynamically sized type + = help: change the field's type to have a statically known size +help: borrowed types always have a statically known size + | +LL | field2: &str, // Unsized + | + +help: the `Box` type always has a statically known size and allocates its contents in the heap + | +LL | field2: Box, // Unsized + | ++++ + + +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:27:9 + | +LL | str, // Unsized + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `str` + = note: no field of an enum variant may have a dynamically sized type + = help: change the field's type to have a statically known size +help: borrowed types always have a statically known size + | +LL | &str, // Unsized + | + +help: the `Box` type always has a statically known size and allocates its contents in the heap + | +LL | Box, // Unsized + | ++++ + + +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:37:17 + | +LL | field2: str, // Unsized + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `str` + = note: no field of an enum variant may have a dynamically sized type + = help: change the field's type to have a statically known size +help: borrowed types always have a statically known size + | +LL | field2: &str, // Unsized + | + +help: the `Box` type always has a statically known size and allocates its contents in the heap + | +LL | field2: Box, // Unsized + | ++++ + + +error[E0277]: the size for values of type `str` cannot be known at compilation time + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:46:13 + | +LL | field2: str, // Unsized + | ^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `str` + = note: no field of a union may have a dynamically sized type + = help: change the field's type to have a statically known size +help: borrowed types always have a statically known size + | +LL | field2: &str, // Unsized + | + +help: the `Box` type always has a statically known size and allocates its contents in the heap + | +LL | field2: Box, // Unsized + | ++++ + + +error[E0740]: field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be used in a union + --> $DIR/ice-non-last-unsized-field-issue-121473.rs:46:5 + | +LL | field2: str, // Unsized + | ^^^^^^^^^^^ + | + = note: union fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` +help: wrap the field type in `ManuallyDrop<...>` + | +LL | field2: std::mem::ManuallyDrop, // Unsized + | +++++++++++++++++++++++ + + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0277, E0740. +For more information about an error, try `rustc --explain E0277`.