diff options
| author | bors <bors@rust-lang.org> | 2023-07-07 15:42:29 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-07-07 15:42:29 +0000 |
| commit | cb80ff132a0e9aa71529b701427e4e6c243b58df (patch) | |
| tree | 5e92cefe9b90e442586342ca94732f18d133b9e0 /compiler/rustc_ty_utils/src | |
| parent | fd68a6ded951bd7b852ab8107007f7145e3ad6ec (diff) | |
| parent | 7aa5f39d3b55eb746d9d64244ed5343874807ac4 (diff) | |
| download | rust-cb80ff132a0e9aa71529b701427e4e6c243b58df.tar.gz rust-cb80ff132a0e9aa71529b701427e4e6c243b58df.zip | |
Auto merge of #113245 - lukas-code:unsizing-sanity-check, r=the8472
sanity check field offsets in unsizeable structs
As promised in https://github.com/rust-lang/rust/pull/112062#issuecomment-1567494994, this PR extends the layout sanity checks to ensure that structs fields don't move around when unsizing and prevent issues like https://github.com/rust-lang/rust/issues/112048 in the future. Like most other layout sanity checks, this only runs on compilers with debug assertions enabled.
Here is how it looks when it fails:
```text
error: internal compiler error: compiler/rustc_ty_utils/src/layout.rs:533:21: unsizing GcNode<std::boxed::Box<i32>> changed field order!
Layout { size: Size(32 bytes), align: AbiAndPrefAlign { abi: Align(8 bytes), pref: Align(8 bytes) }, abi: Aggregate { sized: true }, fields: Arbitrary { offsets: [Size(0 bytes), Size(8 bytes), Size(24 bytes)], memory_index: [0, 1, 2] }, largest_niche: Some(Niche { offset: Size(24 bytes), value: Pointer(AddressSpace(0)), valid_range: 1..=18446744073709551615 }), variants: Single { index: 0 } }
Layout { size: Size(24 bytes), align: AbiAndPrefAlign { abi: Align(8 bytes), pref: Align(8 bytes) }, abi: Aggregate { sized: false }, fields: Arbitrary { offsets: [Size(16 bytes), Size(0 bytes), Size(24 bytes)], memory_index: [1, 0, 2] }, largest_niche: None, variants: Single { index: 0 } }
```
r? `@the8472`
Diffstat (limited to 'compiler/rustc_ty_utils/src')
| -rw-r--r-- | compiler/rustc_ty_utils/src/layout.rs | 101 | ||||
| -rw-r--r-- | compiler/rustc_ty_utils/src/ty.rs | 2 |
2 files changed, 75 insertions, 28 deletions
diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index 9ef9120e294..b67cd96a734 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -463,38 +463,85 @@ fn layout_of_uncached<'tcx>( )); } - tcx.mk_layout( - cx.layout_of_struct_or_enum( + let get_discriminant_type = + |min, max| Integer::repr_discr(tcx, ty, &def.repr(), min, max); + + let discriminants_iter = || { + def.is_enum() + .then(|| def.discriminants(tcx).map(|(v, d)| (v, d.val as i128))) + .into_iter() + .flatten() + }; + + let dont_niche_optimize_enum = def.repr().inhibit_enum_layout_opt() + || def + .variants() + .iter_enumerated() + .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())); + + let maybe_unsized = def.is_struct() + && def.non_enum_variant().tail_opt().is_some_and(|last_field| { + let param_env = tcx.param_env(def.did()); + !tcx.type_of(last_field.did).subst_identity().is_sized(tcx, param_env) + }); + + let Some(layout) = cx.layout_of_struct_or_enum( + &def.repr(), + &variants, + def.is_enum(), + def.is_unsafe_cell(), + tcx.layout_scalar_valid_range(def.did()), + get_discriminant_type, + discriminants_iter(), + dont_niche_optimize_enum, + !maybe_unsized, + ) else { + return Err(error(cx, LayoutError::SizeOverflow(ty))); + }; + + // If the struct tail is sized and can be unsized, check that unsizing doesn't move the fields around. + if cfg!(debug_assertions) + && maybe_unsized + && def.non_enum_variant().tail().ty(tcx, substs).is_sized(tcx, cx.param_env) + { + let mut variants = variants; + let tail_replacement = cx.layout_of(Ty::new_slice(tcx, tcx.types.u8)).unwrap(); + *variants[FIRST_VARIANT].raw.last_mut().unwrap() = tail_replacement.layout; + + let Some(unsized_layout) = cx.layout_of_struct_or_enum( &def.repr(), &variants, def.is_enum(), def.is_unsafe_cell(), tcx.layout_scalar_valid_range(def.did()), - |min, max| Integer::repr_discr(tcx, ty, &def.repr(), min, max), - def.is_enum() - .then(|| def.discriminants(tcx).map(|(v, d)| (v, d.val as i128))) - .into_iter() - .flatten(), - def.repr().inhibit_enum_layout_opt() - || def - .variants() - .iter_enumerated() - .any(|(i, v)| v.discr != ty::VariantDiscr::Relative(i.as_u32())), - { - let param_env = tcx.param_env(def.did()); - def.is_struct() - && match def.variants().iter().next().and_then(|x| x.fields.raw.last()) - { - Some(last_field) => tcx - .type_of(last_field.did) - .subst_identity() - .is_sized(tcx, param_env), - None => false, - } - }, - ) - .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?, - ) + get_discriminant_type, + discriminants_iter(), + dont_niche_optimize_enum, + !maybe_unsized, + ) else { + bug!("failed to compute unsized layout of {ty:?}"); + }; + + let FieldsShape::Arbitrary { offsets: sized_offsets, .. } = &layout.fields else { + bug!("unexpected FieldsShape for sized layout of {ty:?}: {:?}", layout.fields); + }; + let FieldsShape::Arbitrary { offsets: unsized_offsets, .. } = &unsized_layout.fields else { + bug!("unexpected FieldsShape for unsized layout of {ty:?}: {:?}", unsized_layout.fields); + }; + + let (sized_tail, sized_fields) = sized_offsets.raw.split_last().unwrap(); + let (unsized_tail, unsized_fields) = unsized_offsets.raw.split_last().unwrap(); + + if sized_fields != unsized_fields { + bug!("unsizing {ty:?} changed field order!\n{layout:?}\n{unsized_layout:?}"); + } + + if sized_tail < unsized_tail { + bug!("unsizing {ty:?} moved tail backwards!\n{layout:?}\n{unsized_layout:?}"); + } + } + + tcx.mk_layout(layout) } // Types with no meaningful known layout. diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index 466616cd22b..6e5c50492c6 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -103,7 +103,7 @@ fn adt_sized_constraint(tcx: TyCtxt<'_>, def_id: DefId) -> &[Ty<'_>] { let result = tcx.mk_type_list_from_iter( def.variants() .iter() - .filter_map(|v| v.fields.raw.last()) + .filter_map(|v| v.tail_opt()) .flat_map(|f| sized_constraint_for_ty(tcx, def, tcx.type_of(f.did).subst_identity())), ); |
