
# Objective Resolves #3824. `unsafe` code should be the exception, not the norm in Rust. It's obviously needed for various use cases as it's interfacing with platforms and essentially running the borrow checker at runtime in the ECS, but the touted benefits of Bevy is that we are able to heavily leverage Rust's safety, and we should be holding ourselves accountable to that by minimizing our unsafe footprint. ## Solution Deny `unsafe_code` workspace wide. Add explicit exceptions for the following crates, and forbid it in almost all of the others. * bevy_ecs - Obvious given how much unsafe is needed to achieve performant results * bevy_ptr - Works with raw pointers, even more low level than bevy_ecs. * bevy_render - due to needing to integrate with wgpu * bevy_window - due to needing to integrate with raw_window_handle * bevy_utils - Several unsafe utilities used by bevy_ecs. Ideally moved into bevy_ecs instead of made publicly usable. * bevy_reflect - Required for the unsafe type casting it's doing. * bevy_transform - for the parallel transform propagation * bevy_gizmos - For the SystemParam impls it has. * bevy_assets - To support reflection. Might not be required, not 100% sure yet. * bevy_mikktspace - due to being a conversion from a C library. Pending safe rewrite. * bevy_dynamic_plugin - Inherently unsafe due to the dynamic loading nature. Several uses of unsafe were rewritten, as they did not need to be using them: * bevy_text - a case of `Option::unchecked` could be rewritten as a normal for loop and match instead of an iterator. * bevy_color - the Pod/Zeroable implementations were replaceable with bytemuck's derive macros.
74 lines
3.0 KiB
Rust
74 lines
3.0 KiB
Rust
#![allow(unsafe_code)]
|
|
|
|
use libloading::{Library, Symbol};
|
|
use std::ffi::OsStr;
|
|
use thiserror::Error;
|
|
|
|
use bevy_app::{App, CreatePlugin, Plugin};
|
|
|
|
/// Errors that can occur when loading a dynamic plugin
|
|
#[derive(Debug, Error)]
|
|
pub enum DynamicPluginLoadError {
|
|
/// An error occurred when loading a dynamic library.
|
|
#[error("cannot load library for dynamic plugin: {0}")]
|
|
Library(#[source] libloading::Error),
|
|
/// An error occurred when loading a library without a valid Bevy plugin.
|
|
#[error("dynamic library does not contain a valid Bevy dynamic plugin")]
|
|
Plugin(#[source] libloading::Error),
|
|
}
|
|
|
|
/// Dynamically links a plugin at the given path. The plugin must export a function with the
|
|
/// [`CreatePlugin`] signature named `_bevy_create_plugin`.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The specified plugin must be linked against the exact same `libbevy.so` as this program.
|
|
/// In addition the `_bevy_create_plugin` symbol must not be manually created, but instead created
|
|
/// by deriving `DynamicPlugin` on a unit struct implementing [`Plugin`].
|
|
///
|
|
/// Dynamically loading plugins is orchestrated through dynamic linking. When linking against
|
|
/// foreign code, initialization routines may be run (as well as termination routines when the
|
|
/// program exits). The caller of this function is responsible for ensuring these routines are
|
|
/// sound. For more information, please see the safety section of [`libloading::Library::new`].
|
|
pub unsafe fn dynamically_load_plugin<P: AsRef<OsStr>>(
|
|
path: P,
|
|
) -> Result<(Library, Box<dyn Plugin>), DynamicPluginLoadError> {
|
|
// SAFETY: Caller must follow the safety requirements of Library::new.
|
|
let lib = unsafe { Library::new(path).map_err(DynamicPluginLoadError::Library)? };
|
|
|
|
// SAFETY: Loaded plugins are not allowed to specify `_bevy_create_plugin` symbol manually, but
|
|
// must instead automatically generate it through `DynamicPlugin`.
|
|
let func: Symbol<CreatePlugin> = unsafe {
|
|
lib.get(b"_bevy_create_plugin")
|
|
.map_err(DynamicPluginLoadError::Plugin)?
|
|
};
|
|
|
|
// SAFETY: `func` is automatically generated and is guaranteed to return a pointer created using
|
|
// `Box::into_raw`.
|
|
let plugin = unsafe { Box::from_raw(func()) };
|
|
|
|
Ok((lib, plugin))
|
|
}
|
|
|
|
/// An extension trait for [`App`] that allows loading dynamic plugins.
|
|
pub trait DynamicPluginExt {
|
|
/// Dynamically links a plugin at the given path, registering the plugin.
|
|
///
|
|
/// For more details, see [`dynamically_load_plugin`].
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// See [`dynamically_load_plugin`]'s safety section.
|
|
unsafe fn load_plugin<P: AsRef<OsStr>>(&mut self, path: P) -> &mut Self;
|
|
}
|
|
|
|
impl DynamicPluginExt for App {
|
|
unsafe fn load_plugin<P: AsRef<OsStr>>(&mut self, path: P) -> &mut Self {
|
|
// SAFETY: Follows the same safety requirements as `dynamically_load_plugin`.
|
|
let (lib, plugin) = unsafe { dynamically_load_plugin(path).unwrap() };
|
|
std::mem::forget(lib); // Ensure that the library is not automatically unloaded
|
|
plugin.build(self);
|
|
self
|
|
}
|
|
}
|