Stream: t-lang/wg-unsafe-code-guidelines

Topic: pointer providence question


Yato (May 03 2020 at 21:37, on Zulip):

Let's say I have a function like so:

fn copy_pointer<T: ?Sized, U: ?Sized>(a: *mut T, b: *mut U) -> *mut U {
    use std::mem;
    assert_eq!(mem::size_of::<*mut T>(), mem::size_of::<U>());

    ptr::copy_non_overlapping(&(b as *mut u8), (&mut a) as *mut *mut u8, mem::size_of:<*mut T>());
    mem::transmute_copy(&a)
}

Would this be a valid function that returns a copy of b that has the same providence as a?

Yato (May 03 2020 at 21:41, on Zulip):

One more thing, U will always be the type of a field of T. I'm exploring this option as a way to get safe field projections in for cell-project that allow projecting to !Sized fields. If this is not sound, I can fallback to using an offset and supporting projections to Sized fields.

Yato (May 03 2020 at 21:43, on Zulip):

Because this is a projection from a &Cell<T> I know that the pointer is valid, and I can temporarily get a reference to a field. (this will be passed to b). But it would be unsound for me to use that reference as the projection because it will be invalidated by the next write into the &Cell<T>. So I need derive the projection directly from Cell::as_ptr without intermediate references.

RalfJ (May 04 2020 at 13:53, on Zulip):

(FWIW, it's "provenance", not "providence")

RalfJ (May 04 2020 at 13:53, on Zulip):

ptr provenance is preserved by basically all raw pointer operations

RalfJ (May 04 2020 at 13:54, on Zulip):

Would this be a valid function that returns a copy of b that has the same providence as a?

uh... what? I don't think such an operation exists, and it also is not very meaningful at all (depending on the kind of provenance).

RalfJ (May 04 2020 at 13:54, on Zulip):

you cant just separate the provenance of a pointer from its value

RalfJ (May 04 2020 at 13:55, on Zulip):

this smells like an A-B problem to me

RalfJ (May 04 2020 at 13:56, on Zulip):

&Cell<T> are shared read-writable pointers. they are not in general invalidated by writes using other pointers.

RalfJ (May 04 2020 at 13:56, on Zulip):

so this is not necessarily true:

But it would be unsound for me to use that reference as the projection because it will be invalidated by the next write into the &Cell<T>

RalfJ (May 04 2020 at 13:57, on Zulip):

@Yato could you show some concrete self-contained code that you think is wrong because of this invalidation?

Yato (May 04 2020 at 19:54, on Zulip):

I think the following code is UB because I am converting a &i32 to a &Cell<i32>:

struct Pair(i32, i32);

let pair_cell: &Cell<Pair> = ...;

let Pair(first, _) = unsafe { &(*pair_cell.as_ptr()) };
let first_cell: &Cell<i32> = unsafe { &*(first as *const i32 as *const Cell<i32>) };

i could calculate the offset of the field, and use that to construct a &Cell<i32> without an intermediate reference, but that doesn't work if I want to project to a !Sized field. I wanted to see if there was some other way to project to a !Sized field.

Yato (May 04 2020 at 19:59, on Zulip):

For an example of a projection to a !Sized field

struct Foo {
    header: i32,
    unsized_value: dyn Debug,
}

let foo: &Cell<Foo> = ...;

let Foo { unsized_value, .. } = unsafe { &(*foo.as_ptr()) };

// UB version
let unsized_value: &Cell<i32> = unsafe { &*(unsized as *const i32 as *const Cell<i32>) };
// or with `raw_ref_op`

let unsized_value= &raw const (*foo.as_ptr()).unsized;
let unsized_value: &Cell<i32> = unsafe { &*(unsized as *const Cell<i32>) };
RalfJ (May 06 2020 at 19:40, on Zulip):

converting a &i32 to a &Cell<i32>

ah yes that is definitely not okay :D

RalfJ (May 06 2020 at 19:40, on Zulip):

so then you want to take than &i32 address an equip it wth the &Cell<i32> provenance? curious.

RalfJ (May 06 2020 at 19:42, on Zulip):

I think something offset-based is indeed the only way to currently do that, like

/// Return a pointer with the address of `addr` and the provenance of `prov`.
///
/// Safety conditions: TBD
unsafe fn copy_ptr_provenance<T>(prov: *mut T, addr: *const T)  -> *mut T {
  let diff = addr.offset_from(prov);
  prov.offset(diff)
}
RalfJ (May 06 2020 at 19:43, on Zulip):

this requires both ptrs to be in the same allocation, but that should suffice for your usecase @Yato

Last update: Jun 05 2020 at 23:10UTC