Skip to content

Commit

Permalink
reject projecting to fields whose offset we cannot compute
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Dec 12, 2023
1 parent b1613eb commit 9ef1e35
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 75 deletions.
19 changes: 7 additions & 12 deletions compiler/rustc_codegen_ssa/src/mir/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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:
//
Expand Down
11 changes: 7 additions & 4 deletions compiler/rustc_const_eval/src/interpret/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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`.
59 changes: 0 additions & 59 deletions tests/ui/consts/const-eval/issue-91827-extern-types.rs

This file was deleted.

26 changes: 26 additions & 0 deletions tests/ui/extern/extern-types-field-offset.rs
Original file line number Diff line number Diff line change
@@ -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;
}
4 changes: 4 additions & 0 deletions tests/ui/extern/extern-types-field-offset.run.stderr
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 9ef1e35

Please sign in to comment.