i'm implementing a "path" data structure. path in the vector graphics sense, an array of verbs and an array of points.
the memory layout looks as follows:
struct PathMemory {
header: PathHeader,
verbs: [Verb; header.num_verbs],
points: [Point; header.num_points],
}
now, since we obviously can't define such a struct in rust (though it would be nice if we could), this layout is implemented manually using unsafe
.
the user primarily interacts with &'a PathHeader
(which is exposed under the alias Path
).
now to the issue:
i have a method verbs(&self) -> &[Verb]
implemented on PathHeader
, which, according to miri, triggers undefined behavior. (it just works by offsetting the pointer derived from &self
and aligning it. the returned slice contains the correct values.)
the issue seems to be that &self
only borrows the bytes of the header, but the verbs are after the header, of course. - if i'm reading this log correctly:
error: Undefined Behavior: trying to retag from <3947> for SharedReadOnly permission at alloc1950
[0x20], but that tag does not exist in the borrow stack for this location
--> /Users/leddoo/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library
/core/src/slice/raw.rs:100:9
|
100 | &*ptr::slice_from_raw_parts(data, len)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| trying to retag from <3947> for SharedReadOnly permission at alloc1950[0x20], but t
hat tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc1950[0x20..0x24]
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but
the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borro
ws.md for further information
help: <3947> was created by a SharedReadOnly retag at offsets [0x14..0x44]
--> /Users/leddoo/dev/rug/rug/src/path.rs:267:41
|
267 | let ptr: *const Verb = cat_next(self, size_of::<Self>());
| ^^^^
= note: BACKTRACE (of the first span):
= note: inside `std::slice::from_raw_parts::<'_, rug::path::Verb>` at /Users/leddoo/.rustup/t
oolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/slice/raw.rs:100:9:
100:47
note: inside `rug::path::PathData::verbs`
--> /Users/leddoo/dev/rug/rug/src/path.rs:268:9
|
268 | core::slice::from_raw_parts(ptr, self.num_verbs as usize)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside `main`
--> examples/api/src/main.rs:13:22
|
13 | println!("{:?}", path.verbs());
| ^^^^^^^^^^^^
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrac
e
here's the implementation of cat_next
:
#[inline(always)]
pub unsafe fn cat_next<T, U>(base: *const T, base_size: usize) -> *const U {
unsafe { cat_next_ex(base, base_size, core::mem::align_of::<U>()) }
}
#[inline(always)]
pub unsafe fn cat_next_ex<T, U>(base: *const T, base_size: usize, next_align: usize) -> *const U {
let result = ceil_to_multiple_pow2(base as usize + base_size, next_align);
#[cfg(miri)] {
// miri doesn't like int->ptr casts.
let delta = result - base as usize;
return (base as *const u8).add(delta) as *const U;
}
#[cfg(not(miri))] {
return result as *const U;
}
}
#[inline(always)]
pub fn ceil_to_multiple_pow2(x: usize, n: usize) -> usize {
debug_assert!(is_pow2(n));
(x + (n-1)) & !(n-1)
}
i've considered modeling the path memory like so:
struct PathData {
header: PathHeader,
blob: [u8],
}
this doesn't really work for me however, as it would make Path
(a &PathData
in this case) a fat pointer. - though i suppose i could do that in cfg(miri)
builds only
any ideas for how i could make miri understand what's going on?