-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking Issue for layout information behind pointers #69835
Comments
…ruppe re-add Layout::for_value_raw Tracking issue: rust-lang#69835 This was accidentally removed in rust-lang#70362 56cbf2f. Originally added in rust-lang#69079.
Potential bikeshed: maybe these methods should be moved to the |
The safety documentation on these functions is somewhat inaccurate. It states:
But the size of a custom slice DST is not necessarily the sum of the size of its prefix and the size of its slice tail. If the alignment of the prefix is greater than the alignment of the slice type, the compiler will insert additional padding following the slice, which is counted in the full DST size. I stumbled on this issue when investigating custom |
Well, the padding is statically sized, so in that sense, it's part of the statically sized prefix. I also agree that there should be a fallible way to query layout information from pointer metadata, and have an experimental PR #95832 open to determine the cost of making size_of sound to call for arbitrarily sized slice tails (by saturating). |
In what sense is it statically sized? Consider this test program (playground): #![feature(layout_for_ptr)]
use std::{mem, ptr};
const PREFIX: usize = 4;
#[repr(C)]
struct DST {
align: [u64; 0],
prefix: [u8; PREFIX],
slice: [u8],
}
fn main() {
for len in 0..32 {
let ptr = ptr::slice_from_raw_parts(ptr::null::<()>(), len);
let ptr = ptr as *const DST;
let size = unsafe { mem::size_of_val_raw(ptr) };
print!("{} ", size - PREFIX - len);
}
println!();
} You can adjust the size of the prefix. With a 4-byte prefix, this is the output:
These are the differences between the |
Ah, right, there is trailing padding to the alignment. I just read your comment wrong the first time. And also, if it's `#[repr(Rust)], it's good to reiterate that there are formally no layout guarantees anyway, so the compiler is within its rights to do wackier things if it wanted to. The trailing slice is always at the same offset.#![feature(layout_for_ptr)]
use std::{mem, ptr};
const PREFIX: usize = 4;
#[repr(C)]
struct DST {
align: [u64; 0],
prefix: [u8; PREFIX],
slice: [u8],
}
fn main() {
for len in 0..32 {
let ptr = ptr::slice_from_raw_parts(ptr::null::<()>(), len);
let ptr = ptr as *const DST;
let offset = unsafe {
ptr::addr_of!((*ptr).slice)
.cast::<u8>()
.offset_from(ptr.cast())
};
print!("{offset} ");
}
println!();
} prints all 4s. You could argue that "dynamic tail length" includes the dynamic padding to alignment, or we could just add a "+ alignment padding" clause. One way around the issue of |
That makes sense wrt. |
What about |
Perhaps it should be called |
We reviewed this in today's @rust-lang/lang meeting. It seems like these fit into the general story of pointee metadata, and we should consider them together with that. They may potentially want to make use of that metadata rather than operating directly on the pointers. cc #81513, which tracks the more general question. |
cc also rust-lang/lang-team#166 which includes an argument that it may make sense to restrict custom pointee layout to be calculable from the pointer metadata and not allow it to be address/pointee-sensitive. |
Shouldn't these functions guarantee that they are safe to call at least when casting the pointer to a reference and calling |
It's not explicitly stated, but it the case that if the pointer is valid to reborrow as a reference that these functions are safe to call. The listed conditions should be a proper subset of "pointer valid to reborrow." |
@CAD97 they aren't really a proper subset, as they don't account for new unsized types that might be added to Rust in the future. |
I made a PR to fix this: #103372 |
In the interest of stabilizing something, would it be possible to choose the most conservative interpretations for these questions, leaving the door open to future work to loosen the requirements? Ie:
For this, add a safety precondition that the pointer not result in size wrapping. Continue to require as a safety precondition that vtable pointers are always valid.
IIUC, inside the stdlib it's okay to rely on behavior that is not promised publicly, right?
For this, document that |
I've filed a PR with the above recommendations: #117185 This only required documenting the overflow preconditions, since the possibility of panics was already documented and the implementation details of |
It's already a requirement for Those conditions are listed there as the reasons the function needs to be
Yes, but I think you somehow have something backwards. Only permitting
If you're going for the most conservative, it would be to just forbid usage with extern types initially. It's quite common in fact for "raw" unchecked versions of functions to be UB in cases where the safe function would panic or return a safe, if potentially misleading, default result.
I will agree there. To do so, my proposed replacement for the safety section:
This is very slightly more permissive than "valid pointer to I'm not able to make a PR for this right now, but I should be able to sometime tomorrow. |
What is the motivation for requiring the pointer to point to an actual allocation (or for |
The motivation is merely in being conservative and only stabilizing the minimal capability which the language team has already signed off on. It's also a condition marginally simpler to explain than a requirement closer to the actual operational requirement. No other API exists (on stable yet) which has a requirement on pointers weaker than "dereferencable" but stronger than the implicit safety invariant of pointers (whatever that is1). I would much prefer the condition as it's currently (and I originally) specified it, though I might rephrase it a bit given post-2019 understanding of safety/validity requirements. But the underlying presupposition is that a stricter requirement would be easier to stabilize. I'm not quite sure which teams stabilizing the functions as currently specified would fall under. If it's exclusively the domain of libs-api, I could honestly open a stabilization PR a year ago with justifications that the current choices are the correct ones. It's the presumed need to get lang signoff on extending what's possible in stable Rust (and having hacked together partially functional workarounds for code where I've wanted to use this) that's precluded me actively trying to force this through to stable. Since this safety requirement fundamentally needs to talk about the pointer metadata in order to be weaker than "dereferencable," it's thus also entangled with feature(ptr_metadata). And that's a much bigger gate to try to stabilize (and I still need to revive a change request for it). Removing that implicit dependency is a benefit to specifying the requirement here in terms of dereferencability. Footnotes
|
Stabilization should definitively involve t-lang, since this extends what can be expressed in Rust.
The current spec doesn't require ptr_metadata I think? You need to recurse over the type to find the tail field, but I don't think it's needed to talk about ptr_metadata. Dealing with dangling pointers is kind of the point of raw pointers, so I would find it somewhat odd that we would require dereferenceability here. |
Without getting into the weeds of which other features are implicated, I will say that from a user's perspective, this feature wouldn't really be useful if the memory needs to be dereferenceable. The reason I'm advocating for stabilization is to support this design, which synthesizes a raw pointer to a slice DST type in order to determine its minimum size (ie, the size of the value with 0 trailing slice elements). If dereferenceability is a safety precondition, then that use case isn't supported. Speaking more generally, most (maybe all?) cases in which a user has a raw pointer which satisfies the dereferenceability condition, it is possible to call |
This commit implements the recommendation of [1] to make the safety preconditions of the raw pointer layout utilities more conservative, to ease the path towards stabilization. In the future, we may (if we choose) remove some of these restrictions without breaking forwards compatibility. [1]: rust-lang#69835 (comment)
One problem, as I was saying above, is that the use case still isn't supported without either a) the guarantee that a |
Ah good point. I've asked about (a) here: rust-lang/unsafe-code-guidelines#465 (comment) |
Recording here: the fact that valid vtable pointer is considered a safety precondition of |
In terms of the unanswered question
I would say that we anyway need a plan for how to migrate @CAD97 having such a safe function sounds nice, indeed. However, how do you think it would affect these versions here? Is it that their name should then be |
If I have thought about naming some previously, and what I came up with is that the safe version should probably exist and be named as part of the ptr_metadata family, e.g. the eventual end state could logically look like: // no implied baseline bounds
fn size_of<T: Sized>() -> usize;
fn size_of_val<T: DynSized>(val: &T) -> usize;
unsafe fn size_of_val_raw<T: DynSized>(val: *const T) -> usize;
fn Layout::new<T: Sized>() -> Layout;
fn Layout::for_value<T: DynSized>(val: &T) -> Layout;
unsafe fn Layout::for_value_raw<T: DynSized>(val: *const T) -> Layout;
// struct Size(usize in 0..=isize::MAX); // c.f. Alignment
fn Size::of<T: Sized>() -> Size;
fn Size::of_val<T: DynSized>(val: &T) -> Size;
fn Size::with_metadata_of<T: MetaSized>(meta: *const T) -> Option<Size>;
fn Layout::with_metadata<T: MetaSized>(meta: T::Metadata) -> Result<Layout>;
fn Layout::with_metadata_of<T: MetaSized>(meta: *const T) -> Result<Layout>; We could then implement/specify pub unsafe fn size_of_val_unchecked<T: Pointee>(val: *const T) -> usize {
static where T: Sized {
size_of::<T>()
} else where T: MetaSized {
let size = Size::with_metadata_of(val);
assert_unsafe_precondition!(
check_language_ub,
"size_of_val_unchecked requires the pointee to have a valid layout",
(size: Option<Size> = size) => size.is_some()
);
// SAFETY: passed to the caller
unsafe { size.unwrap_unchecked() }.into()
} else where T: DynSized {
// SAFETY: passed to the caller
let val = unsafe { val.as_ref_unchecked() };
size_of_val(val)
} else {
// SAFETY: passed to the caller
unsafe { hint::unreasonable_unchecked() }
}
} While the exact ideal behavior for future unsize kinds which are not (Here, |
I am confused: there's no |
So I would write the spec more like pub unsafe fn size_of_val_unchecked<T: Pointee>(val: *const T) -> usize {
static where T: Sized {
size_of::<T>()
} else where T: MetaSized {
let size = Size::with_metadata_of(val);
assert_unsafe_precondition!(
check_language_ub,
"size_of_val_unchecked requires the pointee to have a valid layout",
(size: Option<Size> = size) => size.is_some()
);
// SAFETY: passed to the caller
unsafe { size.unwrap_unchecked() }.into()
} else {
// SAFETY: passed to the caller
unsafe { size_of_val(&*val) }
}
} |
The feature gate for the issue is
#![feature(layout_for_ptr)]
.This tracks three functions:
core::mem::size_of_val_raw<T: ?Sized>(val: *const T) -> usize
core::mem::align_of_val_raw<T: ?Sized>(val: *const T) -> usize
core::alloc::Layout::for_value_raw<T: ?Sized>(t: *const T) -> Layout
These provide raw-pointer variants of the existing
mem::size_of_val
,mem::align_of_val
, andLayout::for_value
.About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also uses as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
Unresolved Questions
ptr::slice_from_raw_parts
. Trait vtable pointers are currently required to always be valid, but this is not guaranteed and an open question whether this is required of invalid pointers.A
]Rc
's current implementation requires callingLayout::for_value_raw
on a pointer that has beendrop_in_place
'd, so this likely needs to stay possible._of_val
cannot. Additionally, extern types may want to access the value to determine the size (e.g. a null terminated cstr).rust-lang/lang-team#166 is tangentially related, as it serves to document what requirements currently exist on pointee types and getting a known layout from them.
Implementation history
The text was updated successfully, but these errors were encountered: