Description
I've noticed the following pattern is used in allocating a bz_stream
object from Rust within src/mem.rs:
let mut raw = Box::new(mem::zeroed());
assert_eq!(
ffi::BZ2_bzCompressInit(&mut *raw, lvl.level() as c_int, 0, work_factor as c_int),
0
);
Then, the unique borrow created by &mut *raw
is used to create a cyclic data structure between the initialized bz_stream
and its EState
member, state
. For example, in BZ2_bzCompressInit
in bzlib.c, you have the following:
int BZ_API(BZ2_bzCompressInit)
( bz_stream* strm,
... )
{
...
EState* s;
...
s->strm = strm;
...
strm->state = s;
...
}
Every time the allocated bz_stream
object is passed from Rust to C, it's done with &mut *raw
. In Stacked Borrows, it seems like this would invalidate the SharedRW permission associated with the original pointer stored by s->strm = strm;
. In Tree Borrows, it would initially transition the permission for s->strm
to being Reserved
the next time a &mut *raw
is passed, so reading through it would still be allowed.
But, the root bz_stream
allocation is both read and written to through its state
member, so this would still lead to violations of the model eventually. For instance, in copy_input_until_stop
(also in bzlib.c), you have both reads and writes from the EState
pointer s
to its parent bz_stream
:
ADD_CHAR_TO_BLOCK ( s, (UInt32)(*((UChar*)(s->strm->next_in))) );
s->strm->next_in++;
It seems like the fix for this would be to convert the Box
into a raw pointer with Box::into_raw
and pass this raw pointer directly to the FFI. Then, it would be re-wrapped it for deallocation in the implementation of Drop
for Stream
. However, I'm totally unsure if this would be a situation where violating the Stacked Borrows/Tree Borrows models would be problematic. What do you think?