Rollup merge of #132745 - RalfJung:pointee-info-inside-enum, r=DianQK

pointee_info_at: fix logic for recursing into enums

Fixes https://github.com/rust-lang/rust/issues/131834

The logic in `pointee_info_at` was likely written at a time when the null pointer optimization was the *only* enum layout optimization -- and as `Variant::Multiple` kept getting expanded, nobody noticed that the logic is now unsound.

The job of this function is to figure out whether there is a dereferenceable-or-null and aligned pointer at a given offset inside a type. So when we recurse into a multi-variant enum, we better make sure that all the other enum variants must be null! This is the part that was forgotten, and this PR adds it.

The reason this didn't explode in many ways so far is that our references only have 1 niche value (null), so it's not possible on stable to have a multi-variant enum with a dereferenceable pointer and other enum variants that are not null. But with `rustc_layout_scalar_valid_range` attributes one can force such a layout, and if `@the8472's` work on alignment niches ever lands, that will make this possible on stable.
This commit is contained in:
Matthias Krüger 2024-11-09 10:52:03 +01:00 committed by GitHub
commit 6e05afd744
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 20 deletions

View File

@ -1743,15 +1743,23 @@ pub enum PointerKind {
Box { unpin: bool, global: bool }, Box { unpin: bool, global: bool },
} }
/// Note that this information is advisory only, and backends are free to ignore it. /// Encodes extra information we have about a pointer.
/// It can only be used to encode potential optimizations, but no critical information. /// Note that this information is advisory only, and backends are free to ignore it:
/// if the information is wrong, that can cause UB, but if the information is absent,
/// that must always be okay.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct PointeeInfo { pub struct PointeeInfo {
pub size: Size,
pub align: Align,
/// If this is `None`, then this is a raw pointer, so size and alignment are not guaranteed to /// If this is `None`, then this is a raw pointer, so size and alignment are not guaranteed to
/// be reliable. /// be reliable.
pub safe: Option<PointerKind>, pub safe: Option<PointerKind>,
/// If `safe` is `Some`, then the pointer is either null or dereferenceable for this many bytes.
/// On a function argument, "dereferenceable" here means "dereferenceable for the entire duration
/// of this function call", i.e. it is UB for the memory that this pointer points to to be freed
/// while this function is still running.
/// The size can be zero if the pointer is not dereferenceable.
pub size: Size,
/// If `safe` is `Some`, then the pointer is aligned as indicated.
pub align: Align,
} }
impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> { impl<FieldIdx: Idx, VariantIdx: Idx> LayoutData<FieldIdx, VariantIdx> {

View File

@ -1011,25 +1011,41 @@ where
} }
_ => { _ => {
let mut data_variant = match this.variants { let mut data_variant = match &this.variants {
// Within the discriminant field, only the niche itself is // Within the discriminant field, only the niche itself is
// always initialized, so we only check for a pointer at its // always initialized, so we only check for a pointer at its
// offset. // offset.
// //
// If the niche is a pointer, it's either valid (according // Our goal here is to check whether this represents a
// to its type), or null (which the niche field's scalar // "dereferenceable or null" pointer, so we need to ensure
// validity range encodes). This allows using // that there is only one other variant, and it must be null.
// `dereferenceable_or_null` for e.g., `Option<&T>`, and // Below, we will then check whether the pointer is indeed
// this will continue to work as long as we don't start // dereferenceable.
// using more niches than just null (e.g., the first page of
// the address space, or unaligned pointers).
Variants::Multiple { Variants::Multiple {
tag_encoding: TagEncoding::Niche { untagged_variant, .. }, tag_encoding:
TagEncoding::Niche { untagged_variant, niche_variants, niche_start },
tag_field, tag_field,
variants,
.. ..
} if this.fields.offset(tag_field) == offset => { } if variants.len() == 2 && this.fields.offset(*tag_field) == offset => {
Some(this.for_variant(cx, untagged_variant)) let tagged_variant = if untagged_variant.as_u32() == 0 {
VariantIdx::from_u32(1)
} else {
VariantIdx::from_u32(0)
};
assert_eq!(tagged_variant, *niche_variants.start());
if *niche_start == 0 {
// The other variant is encoded as "null", so we can recurse searching for
// a pointer here. This relies on the fact that the codegen backend
// only adds "dereferenceable" if there's also a "nonnull" proof,
// and that null is aligned for all alignments so it's okay to forward
// the pointer's alignment.
Some(this.for_variant(cx, *untagged_variant))
} else {
None
}
} }
Variants::Multiple { .. } => None,
_ => Some(this), _ => Some(this),
}; };

View File

@ -143,7 +143,8 @@ pub struct ArgAttributes {
pub regular: ArgAttribute, pub regular: ArgAttribute,
pub arg_ext: ArgExtension, pub arg_ext: ArgExtension,
/// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call /// The minimum size of the pointee, guaranteed to be valid for the duration of the whole call
/// (corresponding to LLVM's dereferenceable and dereferenceable_or_null attributes). /// (corresponding to LLVM's dereferenceable_or_null attributes, i.e., it is okay for this to be
/// set on a null pointer, but all non-null pointers must be dereferenceable).
pub pointee_size: Size, pub pointee_size: Size,
pub pointee_align: Option<Align>, pub pointee_align: Option<Align>,
} }

View File

@ -1,5 +1,6 @@
//@ compile-flags: -O -C no-prepopulate-passes //@ compile-flags: -O -C no-prepopulate-passes
#![crate_type = "lib"] #![crate_type = "lib"]
#![feature(rustc_attrs)]
#![feature(dyn_star)] #![feature(dyn_star)]
#![feature(allocator_api)] #![feature(allocator_api)]
@ -143,13 +144,28 @@ pub fn indirect_struct(_: S) {}
#[no_mangle] #[no_mangle]
pub fn borrowed_struct(_: &S) {} pub fn borrowed_struct(_: &S) {}
// CHECK: @option_borrow(ptr noalias noundef readonly align 4 dereferenceable_or_null(4) %x) // CHECK: @option_borrow(ptr noalias noundef readonly align 4 dereferenceable_or_null(4) %_x)
#[no_mangle] #[no_mangle]
pub fn option_borrow(x: Option<&i32>) {} pub fn option_borrow(_x: Option<&i32>) {}
// CHECK: @option_borrow_mut(ptr noalias noundef align 4 dereferenceable_or_null(4) %x) // CHECK: @option_borrow_mut(ptr noalias noundef align 4 dereferenceable_or_null(4) %_x)
#[no_mangle] #[no_mangle]
pub fn option_borrow_mut(x: Option<&mut i32>) {} pub fn option_borrow_mut(_x: Option<&mut i32>) {}
// Function that must NOT have `dereferenceable` or `align`.
#[rustc_layout_scalar_valid_range_start(16)]
pub struct RestrictedAddress(&'static i16);
enum E {
A(RestrictedAddress),
B,
C,
}
// If the `nonnull` ever goes missing, you might have to tweak the
// scalar_valid_range on `RestrictedAddress` to get it back. You
// might even have to add a `rustc_layout_scalar_valid_range_end`.
// CHECK: @nonnull_and_nondereferenceable(ptr noundef nonnull %_x)
#[no_mangle]
pub fn nonnull_and_nondereferenceable(_x: E) {}
// CHECK: @raw_struct(ptr noundef %_1) // CHECK: @raw_struct(ptr noundef %_1)
#[no_mangle] #[no_mangle]