An issue was created regarding my recent WASM as a Platform for Abstraction blog post pointing out potential undefined behaviour.
I've created an example of what we're doing on the playground. It could be minimised a bit further, but this topic is subtle so I don't want to accidentally miss something.
The scenario is like this:
- There's some
Ctx
object which has avoid *
property for attaching arbitrary data to the context - We create a
State
object as a local variable and makecontext.data
point to it - To prevent accidental aliased pointers, shadow the
state
variable (let state = ()
) - Call a function with the
Ctx
that castscontext.data
toState
and does something non-trivial with it - Catch any panics which the function may have triggered
- Set set
context.data = NULL
so we can't accidentally have any dangling pointers when this function returns (context
is a long-lived object) - Continue unwinding or return the function's result
Example
use std::os::raw::c_void;
use std::panic::{self, AssertUnwindSafe};
use std::ptr;
struct Ctx {
data: *mut c_void,
}
struct State {
something_important: u32,
}
/// # Safety
///
/// This function relies on `ctx.data` pointing to a valid `State`.
unsafe fn execute_function_with_ctx(ctx: &mut Ctx) {
if ctx.data.is_null() {
return;
}
let state = &mut *(ctx.data as *mut State);
// do something important using `state`
state.something_important += 1;
}
fn main() {
// pretend this was created elsewhere and passed into the function
let mut ctx = Ctx {
data: ptr::null_mut(),
};
// set up the state
let mut state = State {
something_important: 42,
};
ctx.data = &mut state as *mut State as *mut c_void;
// we can't use the old state variable any more (we'd have aliased
// pointers) so deliberately shadow it
#[allow(unused_variables)]
let state = ();
let outcome =
unsafe { panic::catch_unwind(AssertUnwindSafe(|| execute_function_with_ctx(&mut ctx))) };
// make sure the data pointer is always cleared so we don't have a dangling pointer
ctx.data = ptr::null_mut();
// continue unwinding or return the result
match outcome {
Ok(value) => println!("Returned {:?}", value),
Err(panic_object) => panic::resume_unwind(panic_object),
}
}
I also ran the playground example through clippy and miri and it doesn't see anything unsound.