编辑:张汉东

汇总一些 Unsafe Rust 编码技巧,欢迎补充。


编写 Unsafe Rust 的标准规范

来源:wasmtime

  1. /// Invokes this WebAssembly function with the specified parameters.
  2. ///
  3. /// Returns either the results of the call, or a [`Trap`] if one happened.
  4. ///
  5. /// For more information, see the [`Func::typed`] and [`Func::call`]
  6. /// documentation.
  7. ///
  8. /// # Panics
  9. ///
  10. /// This function will panic if it is called when the underlying [`Func`] is
  11. /// connected to an asynchronous store.
  12. pub fn call(&self, params: Params) -> Result<Results, Trap> {
  13. assert!(
  14. !cfg!(feature = "async") || !self.func.store().async_support(),
  15. "must use `call_async` with async stores"
  16. );
  17. unsafe { self._call(params) }
  18. }

当函数中调用了 Unsafe 函数,必须对其进行安全抽象。

上面代码示例中,使用 assert! 宏,将 _call调用控制在了安全边界内,所以函数 call 目前是一个安全的函数,所以不需要在 fn 前面增加 unsafe 标签。

  1. unsafe fn _call(&self, params: Params) -> Result<Results, Trap> {
  2. // Validate that all runtime values flowing into this store indeed
  3. // belong within this store, otherwise it would be unsafe for store
  4. // values to cross each other.
  5. if !params.compatible_with_store(&self.func.instance.store) {
  6. return Err(Trap::new(
  7. "attempt to pass cross-`Store` value to Wasm as function argument",
  8. ));
  9. }
  10. // ...
  11. // ignore others codes
  12. // ...
  13. // This can happen if we early-trap due to interrupts or other
  14. // pre-flight checks, so we need to be sure the parameters are at least
  15. // dropped at some point.
  16. if !called {
  17. drop(params.assume_init());
  18. }
  19. debug_assert_eq!(result.is_ok(), returned);
  20. result?;
  21. Ok(ret.assume_init())
  22. }

对于 _call 函数来说,因为无法在函数内验证所有传入的运行时值是否在合法的安全边界,所以需要将其标记为 Unsafe 函数,即在 fn 前面加上 unsafe 标签。除此之外,还必须在函数内脆弱的地方,加上必须的注释来说明什么情况下会突破安全边界。

  1. /// A trait implemented for types which can be arguments and results for
  2. /// closures passed to [`Func::wrap`] as well as parameters to [`Func::typed`].
  3. ///
  4. /// This trait should not be implemented by user types. This trait may change at
  5. /// any time internally. The types which implement this trait, however, are
  6. /// stable over time.
  7. ///
  8. /// For more information see [`Func::wrap`] and [`Func::typed`]
  9. pub unsafe trait WasmTy {
  10. #[doc(hidden)]
  11. type Abi: Copy;
  12. #[doc(hidden)]
  13. #[inline]
  14. fn typecheck(ty: crate::ValType) -> Result<()> {
  15. if ty == Self::valtype() {
  16. Ok(())
  17. } else {
  18. bail!("expected {} found {}", Self::valtype(), ty)
  19. }
  20. }
  21. #[doc(hidden)]
  22. fn valtype() -> ValType;
  23. #[doc(hidden)]
  24. fn compatible_with_store(&self, store: &Store) -> bool;
  25. #[doc(hidden)]
  26. fn into_abi(self, store: &Store) -> Self::Abi;
  27. #[doc(hidden)]
  28. unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self;
  29. }

对于上面的 trait ,因为是内部使用,随时可能发生改变。所以标记为 Unsafe ,并加上注释提示该 trait 不该又库用户自己实现,而是由维护者在内部为指定类型实现,这些类型应该是稳定的。如果用户想自己实现,那么要明白它是 Unsafe 的。

所以,不一定是出于内存安全才指定 Unsafe ,也可以作为一种和库用户的约定。

在 FFi 时方便调用 Rust 闭包

  1. use std::os::raw::c_void;
  2. pub type Callback = unsafe extern "C" fn(user_data: *mut c_void, arg: i32) -> i32;
  3. pub unsafe extern "C" fn execute_a_closure(arg: i32, cb: Callback, user_data: *mut c_void) -> i32 {
  4. cb(user_data, arg)
  5. }
  6. /// 获取一个可以用作[`Callback`]的函数指针,该函数指针将指向闭包的指针作为其`user_data`。
  7. pub fn raw_callback<F>(_closure: &F) -> Callback
  8. where
  9. F: FnMut(i32) -> i32,
  10. {
  11. unsafe extern "C" fn wrapper<P>(user_data: *mut c_void, arg: i32) -> i32
  12. where
  13. P: FnMut(i32) -> i32,
  14. {
  15. let cb = &mut *(user_data as *mut P);
  16. cb(arg)
  17. }
  18. wrapper::<F>
  19. }
  20. fn main() {
  21. let mut calls = 0;
  22. let mut closure = |arg: i32| {
  23. calls += 1;
  24. arg
  25. };
  26. unsafe {
  27. let func = raw_callback(&closure);
  28. let got = execute_a_closure(42, func, &mut closure as *mut _ as *mut c_void);
  29. assert_eq!(got, 42);
  30. assert_eq!(calls, 1);
  31. }
  32. }

经过 Miri 检测没有 UB。