tczajka
January 30, 2023, 4:50pm
23
Inserting a transmute
call affects the program. You're ascribing a safe behavior to transmute
for invalid values that it doesn't have.
See the documentation :
Both the argument and the result must be valid at their given type. Violating this condition leads to undefined behavior . The compiler will generate code assuming that you, the programmer, ensure that there will never be undefined behavior . It is therefore your responsibility to guarantee that every value passed to transmute
is valid at both types Src
and Dst
. Failing to uphold this condition may lead to unexpected and unstable compilation results. This makes transmute
incredibly unsafe . transmute
should be the absolute last resort.
1 Like
The key part it, actually, here:
The answer is, of course, is based on the fact that compiler have no intuition and it also couldn't understand what correct program is suppose to do.
Compiler just applies bunch of optimisation passes and that's it.
Now, compiler developers have intuition and understand what correct program is supposed to do.
Unfortunately they coined the name âundefined behaviorâ for âsomething that valid program shouldn't ever doâ and this causes us no end of grief.
Because it's âbehaviorâ (even if âundefinedâ) people assume it's something âallowed but weirdâ⌠and that is where whole confusion starts.
4 Likes
Miiao
January 30, 2023, 5:12pm
25
I have rewritten the code:
fn foo<T: ?Sized>(src: Option<&T>) -> *const T
where <T as core::ptr::Pointee>::Metadata: Default
{
src.map_or(core::ptr::from_raw_parts(core::ptr::null(), <<T as core::ptr::Pointee>::Metadata as Default>::default()), |x| x as *const T)
}
Now, @H2CO3 's code works as expected
fn main() {
let x = foo::<str>(None);
let n = (x as *const [u8]).len();
if n == 0 {
println!("hello {n}"); // hello 0
}
}
In case of an SST, it generates the same asm. In case of a DST, we get two more mnemonics:
mov rdx, rsi
mov rax, rdi
test rdi, rdi ; if address == 0
cmove rdx, rdi ; replace metadata with address
xfix
January 31, 2023, 4:04pm
26
And to avoid nightly features, it's possible to write this like that. Note that this is more restrictive than using std::ptr::Pointee::Metadata
(it only supports slices and strings), but it has an advantage of working on stable.
use std::ptr;
const fn ptr_from_option<T>(option: Option<&T>) -> *const T
where
T: ?Sized + NullPtr,
{
if let Some(x) = option {
x
} else {
T::NULL_PTR
}
}
trait NullPtr {
const NULL_PTR: *const Self;
}
impl<T> NullPtr for T {
const NULL_PTR: *const T = ptr::null();
}
impl<T> NullPtr for [T] {
const NULL_PTR: *const [T] = ptr::slice_from_raw_parts(ptr::null(), 0);
}
impl NullPtr for str {
const NULL_PTR: *const str = <[u8]>::NULL_PTR as *const str;
}
fn main() {
dbg!(ptr_from_option(Some(&[1, 2, 3])));
dbg!(ptr_from_option::<str>(None));
}
1 Like
chrefr
January 31, 2023, 6:50pm
28
Is null a valid vtable? This is not obvious. I think the consensus is that it is not UB to produce a trait object pointer with a null vtable, but it is unsound to expose it to safe code.
Also, the layout of wide pointers is not guaranteed, so you cannot rely on all-zeroes being a valid bit pattern for them.
1 Like
xfix
January 31, 2023, 8:54pm
29
This is UB for dyn
types. The following program:
fn foo<T: ?Sized>(src: Option<&T>) -> *const T {
src.map_or(unsafe {core::mem::zeroed()}, |x| x as *const T)
}
fn main() {
foo::<dyn std::any::Any>(None);
}
Will print the following:
thread 'main' panicked at 'attempted to zero-initialize type `*const dyn core::any::Any`, which is invalid', src/main.rs:2:24
2 Likes
Don't do that, trait object pointers are basically this:
struct TraitObject{
ptr: *const (),
vtable: &'static VTable,
}
Null vtables are instant UB.
1 Like
cuviper
January 31, 2023, 9:38pm
31
AFAIK the validity vs. safety invariants are not yet decided for vtables, but treating that as UB is the conservative bet in the meantime.
opened 12:42PM - 29 Jan 21 UTC
T-lang
T-libs-api
C-tracking-issue
S-tracking-design-concerns
This is a tracking issue for the RFC 2580 "Pointer metadata & VTable" (rust-lang⌠/rfcs#2580).
The feature gate for the issue is `#![feature(ptr_metadata)]`.
### About tracking issues
Tracking issues are used to record the overall progress of implementation.
They are also used 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.
### Steps
- [x] Implement the RFC (cc @rust-lang/libs @rust-lang/lang -- can anyone write up mentoring
instructions?)
- [ ] Adjust documentation ([see instructions on rustc-dev-guide][doc-guide])
- [ ] Stabilization PR ([see instructions on rustc-dev-guide][stabilization-guide])
[stabilization-guide]: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html#stabilization-pr
[doc-guide]: https://rustc-dev-guide.rust-lang.org/stabilization_guide.html#documentation-prs
### Unresolved Questions
Language-level:
* Is it, or should it be UB (through validity or safety invariants) to have a raw trait object wide pointer with an dangling vtable pointer? A null vtable pointer? If not, `DynMetadata` methods like `size` may need to be `unsafe fn`. Or maybe something like `*const ()` should be metadata of trait objects instead of `DynMetadata`. <br>Right now, there is some inconsistency here: `size_of_val_raw(ptr)` is unsafe, but `metadta(ptr).size_of()` does the same thing and is safe.
API level:
* Is `*const ()` appropriate for the data component of pointers? Or should it be `*const u8`? Or `*const Opaque` with some new `Opaque` type? (Respectively `*mut ()` and `NonNull<()>`)
* Should `ptr::from_raw_parts` and friends be `unsafe fn`?
* Should `Thin` be added as a supertrait of `Sized`? Or could it ever make sense to have fat pointers to statically-sized types?
* Should `DynMetadata` not have a type parameter? This might reduce monomorphization cost, but would force that the size, alignment, and destruction pointers be in the same location (offset) for every vtable. But keeping them in the same location is probaly desirable anyway to keep code size small.
API bikesheds:
* Name of new items: `Pointee` (v.s. Referent?), `Thin` (`ThinPointee`?), `DynMetadata` (`VTablePtr`?), etc
* Location of new items in `core::ptr`. For example: should `Thin` be in `core::marker` instead?
### Implementation history
- [ ] #81172 Initial implementation
### Tracked APIs
Last updated for https://github.com/rust-lang/rust/pull/81172.
```rust
pub trait Pointee {
/// One of `()`, `usize`, or `DynMetadata<dyn SomeTrait>`
type Metadata: Copy + Send + Sync + Ord + Hash + Unpin;
}
pub trait Thin = Pointee<Metadata = ()>;
pub const fn metadata<T: ?Sized>(ptr: *const T) -> <T as Pointee>::Metadata {}
pub const fn from_raw_parts<T: ?Sized>(*const (), <T as Pointee>::Metadata) -> *const T {}
pub const fn from_raw_parts_mut<T: ?Sized>(*mut (), <T as Pointee>::Metadata) -> *mut T {}
impl<T: ?Sized> NonNull<T> {
pub const fn from_raw_parts(NonNull<()>, <T as Pointee>::Metadata) -> NonNull<T> {}
/// Convenience for `(ptr.cast(), metadata(ptr))`
pub const fn to_raw_parts(self) -> (NonNull<()>, <T as Pointee>::Metadata) {}
}
impl<T: ?Sized> *const T {
pub const fn to_raw_parts(self) -> (*const (), <T as Pointee>::Metadata) {}
}
impl<T: ?Sized> *mut T {
pub const fn to_raw_parts(self) -> (*mut (), <T as Pointee>::Metadata) {}
}
/// `<dyn SomeTrait as Pointee>::Metadata == DynMetadata<dyn SomeTrait>`
pub struct DynMetadata<Dyn: ?Sized> {
// Private pointer to vtable
}
impl<Dyn: ?Sized> DynMetadata<Dyn> {
pub fn size_of(self) -> usize {}
pub fn align_of(self) -> usize {}
pub fn layout(self) -> crate::alloc::Layout {}
}
unsafe impl<Dyn: ?Sized> Send for DynMetadata<Dyn> {}
unsafe impl<Dyn: ?Sized> Sync for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Debug for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Unpin for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Copy for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Clone for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Eq for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> PartialEq for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Ord for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> PartialOrd for DynMetadata<Dyn> {}
impl<Dyn: ?Sized> Hash for DynMetadata<Dyn> {}
```
opened 09:34AM - 12 Jul 19 UTC
C-active-discussion-topic
T-validity
The discussions at https://github.com/rust-lang/unsafe-code-guidelines/issues/76⌠and https://github.com/rust-lang/unsafe-code-guidelines/issues/77 are about the validity invariant that all references have to maintain in general; this issue here is specifically about the validity invariant of the metadata component of *any* fat pointer -- which do not have to be references! `Rc<[u8]>` is also a fat pointer. The expected metadata depends on the type of the "unsized tail".
For slices, it seems clear that the invariant will be the same as that for `usize`. But for `dyn Trait` pointers, what should we require about the vtable? My guess would have been *at least* as much as an `&[usize; 3]` (a vtable has at least 3 slots: size, alignment and drop function, not necessarily in that order). But in [this forum thread](https://users.rust-lang.org/t/why-cant-weak-new-be-used-with-a-trait/29976), the valid question came up why `Weak::new` does not work for `dyn Trait`, and indeed it could if we could "fake" the fat pointer component (though doing that generically seems hard).
And then there is the question how this interacts with eventual custom/user-defined DSTs.
1 Like
Miiao
February 1, 2023, 5:19pm
32
My bad. Thought &dyn Any
is (*const (), &'static VTable)
and *const dyn Any
is (*const (), *const VTable)
.
system
Closed
May 4, 2023, 1:00am
35
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.