diff --git a/src/any.rs b/src/any.rs index 7ce26170..483eee84 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,10 +1,11 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. +use crate::parser::INT; +use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; + #[cfg(not(feature = "no_module"))] use crate::module::Module; -use crate::parser::INT; - #[cfg(not(feature = "no_float"))] use crate::parser::FLOAT; @@ -298,37 +299,6 @@ impl Default for Dynamic { } } -/// Cast a type into another type. -fn unsafe_try_cast(a: A) -> Option { - if TypeId::of::() == a.type_id() { - // SAFETY: Just checked we have the right type. We explicitly forget the - // value immediately after moving out, removing any chance of a destructor - // running or value otherwise being used again. - unsafe { - let ret: B = ptr::read(&a as *const _ as *const B); - mem::forget(a); - Some(ret) - } - } else { - None - } -} - -/// Cast a Boxed type into another type. -fn unsafe_cast_box(item: Box) -> Result, Box> { - // Only allow casting to the exact same type - if TypeId::of::() == TypeId::of::() { - // SAFETY: just checked whether we are pointing to the correct type - unsafe { - let raw: *mut dyn Any = Box::into_raw(item as Box); - Ok(Box::from_raw(raw as *mut T)) - } - } else { - // Return the consumed item for chaining. - Err(item) - } -} - impl Dynamic { /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// diff --git a/src/engine.rs b/src/engine.rs index 5f730730..7ef00d45 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,6 +8,7 @@ use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST}; +use crate::r#unsafe::unsafe_cast_var_name; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -127,6 +128,11 @@ impl> From for Target<'_> { } /// A type that holds all the current states of the Engine. +/// +/// # Safety +/// +/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via +/// direct lifetime casting. #[derive(Debug, Clone, Copy)] pub struct State<'a> { /// Global script-defined functions. @@ -265,24 +271,6 @@ impl DerefMut for FunctionsLib { } } -/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow` of -/// another lifetime. This is mainly used to let us push a block-local variable into the -/// current `Scope` without cloning the variable name. Doing this is safe because all local -/// variables in the `Scope` are cleared out before existing the block. -/// -/// Force-casting a local variable lifetime to the current `Scope`'s larger lifetime saves -/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. -fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> { - // If not at global level, we can force-cast - if state.scope_level > 0 { - // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it - // this is safe because all local variables are cleared at the end of the block - unsafe { mem::transmute::<_, &'s str>(name) }.into() - } else { - name.to_string().into() - } -} - /// Rhai main scripting engine. /// /// ``` diff --git a/src/lib.rs b/src/lib.rs index f82bc6d3..f5f1ad87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ mod result; mod scope; mod stdlib; mod token; +mod r#unsafe; mod utils; pub use any::Dynamic; diff --git a/src/unsafe.rs b/src/unsafe.rs new file mode 100644 index 00000000..47d5b40c --- /dev/null +++ b/src/unsafe.rs @@ -0,0 +1,68 @@ +//! A module containing all unsafe code. + +use crate::any::Variant; +use crate::engine::State; +use crate::utils::StaticVec; + +use crate::stdlib::{ + any::{Any, TypeId}, + borrow::Cow, + boxed::Box, + mem, ptr, + string::ToString, + vec::Vec, +}; + +/// Cast a type into another type. +pub fn unsafe_try_cast(a: A) -> Option { + if TypeId::of::() == a.type_id() { + // SAFETY: Just checked we have the right type. We explicitly forget the + // value immediately after moving out, removing any chance of a destructor + // running or value otherwise being used again. + unsafe { + let ret: B = ptr::read(&a as *const _ as *const B); + mem::forget(a); + Some(ret) + } + } else { + None + } +} + +/// Cast a Boxed type into another type. +pub fn unsafe_cast_box(item: Box) -> Result, Box> { + // Only allow casting to the exact same type + if TypeId::of::() == TypeId::of::() { + // SAFETY: just checked whether we are pointing to the correct type + unsafe { + let raw: *mut dyn Any = Box::into_raw(item as Box); + Ok(Box::from_raw(raw as *mut T)) + } + } else { + // Return the consumed item for chaining. + Err(item) + } +} + +/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow` of +/// another lifetime. This is mainly used to let us push a block-local variable into the +/// current `Scope` without cloning the variable name. Doing this is safe because all local +/// variables in the `Scope` are cleared out before existing the block. +/// +/// Force-casting a local variable lifetime to the current `Scope`'s larger lifetime saves +/// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. +pub fn unsafe_cast_var_name<'s>(name: &str, state: &State) -> Cow<'s, str> { + // If not at global level, we can force-cast + if state.scope_level > 0 { + // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it + // this is safe because all local variables are cleared at the end of the block + unsafe { mem::transmute::<_, &'s str>(name) }.into() + } else { + name.to_string().into() + } +} + +/// Provide a type instance that is memory-zeroed. +pub fn unsafe_zeroed() -> T { + unsafe { mem::MaybeUninit::zeroed().assume_init() } +} diff --git a/src/utils.rs b/src/utils.rs index 4e53878d..f61d24ea 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ //! Module containing various utility types and functions. +use crate::r#unsafe::unsafe_zeroed; + use crate::stdlib::{ any::TypeId, fmt, @@ -73,16 +75,6 @@ impl PartialEq for StaticVec { impl Eq for StaticVec {} -impl Default for StaticVec { - fn default() -> Self { - Self { - len: 0, - list: unsafe { mem::MaybeUninit::zeroed().assume_init() }, - more: Vec::new(), - } - } -} - impl FromIterator for StaticVec { fn from_iter>(iter: X) -> Self { let mut vec = StaticVec::new(); @@ -95,6 +87,16 @@ impl FromIterator for StaticVec { } } +impl Default for StaticVec { + fn default() -> Self { + Self { + len: 0, + list: unsafe_zeroed(), + more: Vec::new(), + } + } +} + impl StaticVec { /// Create a new `StaticVec`. pub fn new() -> Self { @@ -105,7 +107,7 @@ impl StaticVec { if self.len == self.list.len() { // Move the fixed list to the Vec for x in 0..self.list.len() { - let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() }; + let def_val: T = unsafe_zeroed(); self.more .push(mem::replace(self.list.get_mut(x).unwrap(), def_val)); } @@ -126,7 +128,7 @@ impl StaticVec { let result = if self.len <= 0 { panic!("nothing to pop!") } else if self.len <= self.list.len() { - let def_val: T = unsafe { mem::MaybeUninit::zeroed().assume_init() }; + let def_val: T = unsafe_zeroed(); mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val) } else { let r = self.more.pop().unwrap();