reject projecting to fields whose offset we cannot compute

This commit is contained in:
Ralf Jung 2023-12-02 12:43:56 +01:00
parent b1613ebc43
commit 9ef1e35166
7 changed files with 85 additions and 75 deletions

View File

@ -99,6 +99,8 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
let offset = self.layout.fields.offset(ix);
let effective_field_align = self.align.restrict_for_offset(offset);
// `simple` is called when we don't need to adjust the offset to
// the dynamic alignment of the field.
let mut simple = || {
let llval = match self.layout.abi {
_ if offset.bytes() == 0 => {
@ -141,28 +143,21 @@ impl<'a, 'tcx, V: CodegenObject> PlaceRef<'tcx, V> {
};
// Simple cases, which don't need DST adjustment:
// * no metadata available - just log the case
// * known alignment - sized types, `[T]`, `str` or a foreign type
// * known alignment - sized types, `[T]`, `str`
// * offset 0 -- rounding up to alignment cannot change the offset
// Note that looking at `field.align` is incorrect since that is not necessarily equal
// to the dynamic alignment of the type.
match field.ty.kind() {
_ if self.llextra.is_none() => {
debug!(
"unsized field `{}`, of `{:?}` has no metadata for adjustment",
ix, self.llval
);
return simple();
}
_ if field.is_sized() => return simple(),
ty::Slice(..) | ty::Str | ty::Foreign(..) => return simple(),
ty::Slice(..) | ty::Str => return simple(),
_ if offset.bytes() == 0 => return simple(),
_ => {}
}
// We need to get the pointer manually now.
// We do this by casting to a `*i8`, then offsetting it by the appropriate amount.
// We do this instead of, say, simply adjusting the pointer from the result of a GEP
// because the field may have an arbitrary alignment in the LLVM representation
// anyway.
// because the field may have an arbitrary alignment in the LLVM representation.
//
// To demonstrate:
//

View File

@ -174,12 +174,15 @@ where
};
(base_meta, offset.align_to(align))
}
None => {
// For unsized types with an extern type tail we perform no adjustments.
// NOTE: keep this in sync with `PlaceRef::project_field` in the codegen backend.
assert!(matches!(base_meta, MemPlaceMeta::None));
None if offset == Size::ZERO => {
// If the offset is 0, then rounding it up to alignment wouldn't change anything,
// so we can do this even for types where we cannot determine the alignment.
(base_meta, offset)
}
None => {
// We don't know the alignment of this field, so we cannot adjust.
throw_unsup_format!("`extern type` does not have a known offset")
}
}
} else {
// base_meta could be present; we might be accessing a sized field of an unsized

View File

@ -0,0 +1,32 @@
// Test that we can handle unsized types with an extern type tail part.
// Regression test for issue #91827.
#![feature(extern_types)]
use std::ptr::addr_of;
extern "C" {
type Opaque;
}
struct Newtype(Opaque);
struct S {
i: i32,
a: Opaque,
}
const NEWTYPE: () = unsafe {
// Projecting to the newtype works, because it is always at offset 0.
let x: &Newtype = unsafe { &*(1usize as *const Newtype) };
let field = &x.0;
};
const OFFSET: () = unsafe {
// This needs to compute the field offset, but we don't know the type's alignment, so this fail.
let x: &S = unsafe { &*(1usize as *const S) };
let field = &x.a; //~ERROR: evaluation of constant value failed
//~| does not have a known offset
};
fn main() {}

View File

@ -0,0 +1,9 @@
error[E0080]: evaluation of constant value failed
--> $DIR/issue-91827-extern-types-field-offset.rs:28:17
|
LL | let field = &x.a;
| ^^^^ `extern type` does not have a known offset
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.

View File

@ -1,59 +0,0 @@
// run-pass
//
// Test that we can handle unsized types with an extern type tail part.
// Regression test for issue #91827.
#![feature(extern_types)]
use std::ptr::addr_of;
extern "C" {
type Opaque;
}
unsafe impl Sync for Opaque {}
#[repr(C)]
pub struct List<T> {
len: usize,
data: [T; 0],
tail: Opaque,
}
#[repr(C)]
pub struct ListImpl<T, const N: usize> {
len: usize,
data: [T; N],
}
impl<T> List<T> {
const fn as_slice(&self) -> &[T] {
unsafe {
let ptr = addr_of!(self.tail) as *const T;
std::slice::from_raw_parts(ptr, self.len)
}
}
}
impl<T, const N: usize> ListImpl<T, N> {
const fn as_list(&self) -> &List<T> {
unsafe { std::mem::transmute(self) }
}
}
pub static A: ListImpl<u128, 3> = ListImpl {
len: 3,
data: [5, 6, 7],
};
pub static A_REF: &'static List<u128> = A.as_list();
pub static A_TAIL_OFFSET: isize = tail_offset(A.as_list());
const fn tail_offset<T>(list: &List<T>) -> isize {
unsafe { (addr_of!(list.tail) as *const u8).offset_from(list as *const List<T> as *const u8) }
}
fn main() {
assert_eq!(A_REF.as_slice(), &[5, 6, 7]);
// Check that interpreter and code generation agree about the position of the tail field.
assert_eq!(A_TAIL_OFFSET, tail_offset(A_REF));
}

View File

@ -0,0 +1,26 @@
// run-fail
// check-run-results
// normalize-stderr-test "panicking\.rs:\d+:\d+:" -> "panicking.rs:"
#![feature(extern_types)]
extern "C" {
type Opaque;
}
struct Newtype(Opaque);
struct S {
i: i32,
a: Opaque,
}
fn main() {
// Projecting to the newtype works, because it is always at offset 0.
let x: &Newtype = unsafe { &*(1usize as *const Newtype) };
let field = &x.0;
// This needs to compute the field offset, but we don't know the type's alignment,
// so this panics.
let x: &S = unsafe { &*(1usize as *const S) };
let field = &x.a;
}

View File

@ -0,0 +1,4 @@
thread 'main' panicked at library/core/src/panicking.rs:
attempted to compute the size or alignment of extern type `Opaque`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread caused non-unwinding panic. aborting.