Consolidate all unsafe code under one single file.

This commit is contained in:
Stephen Chung 2020-05-14 18:27:22 +08:00
parent 5c61827c7c
commit 5d5ceb4049
5 changed files with 92 additions and 63 deletions

View File

@ -1,10 +1,11 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! 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"))] #[cfg(not(feature = "no_module"))]
use crate::module::Module; use crate::module::Module;
use crate::parser::INT;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT; use crate::parser::FLOAT;
@ -298,37 +299,6 @@ impl Default for Dynamic {
} }
} }
/// Cast a type into another type.
fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
if TypeId::of::<B>() == 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<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
// Only allow casting to the exact same type
if TypeId::of::<X>() == TypeId::of::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe {
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
Ok(Box::from_raw(raw as *mut T))
}
} else {
// Return the consumed item for chaining.
Err(item)
}
}
impl Dynamic { impl Dynamic {
/// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is.
/// ///

View File

@ -8,6 +8,7 @@ use crate::module::Module;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST}; use crate::parser::{Expr, FnAccess, FnDef, ReturnType, SharedFnDef, Stmt, AST};
use crate::r#unsafe::unsafe_cast_var_name;
use crate::result::EvalAltResult; use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position; use crate::token::Position;
@ -127,6 +128,11 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
} }
/// A type that holds all the current states of the Engine. /// 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)] #[derive(Debug, Clone, Copy)]
pub struct State<'a> { pub struct State<'a> {
/// Global script-defined functions. /// 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<str>` 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. /// Rhai main scripting engine.
/// ///
/// ``` /// ```

View File

@ -85,6 +85,7 @@ mod result;
mod scope; mod scope;
mod stdlib; mod stdlib;
mod token; mod token;
mod r#unsafe;
mod utils; mod utils;
pub use any::Dynamic; pub use any::Dynamic;

68
src/unsafe.rs Normal file
View File

@ -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: Any, B: Any>(a: A) -> Option<B> {
if TypeId::of::<B>() == 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<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, Box<X>> {
// Only allow casting to the exact same type
if TypeId::of::<X>() == TypeId::of::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe {
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
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<str>` 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>() -> T {
unsafe { mem::MaybeUninit::zeroed().assume_init() }
}

View File

@ -1,5 +1,7 @@
//! Module containing various utility types and functions. //! Module containing various utility types and functions.
use crate::r#unsafe::unsafe_zeroed;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
fmt, fmt,
@ -73,16 +75,6 @@ impl<T: PartialEq> PartialEq for StaticVec<T> {
impl<T: Eq> Eq for StaticVec<T> {} impl<T: Eq> Eq for StaticVec<T> {}
impl<T> Default for StaticVec<T> {
fn default() -> Self {
Self {
len: 0,
list: unsafe { mem::MaybeUninit::zeroed().assume_init() },
more: Vec::new(),
}
}
}
impl<T> FromIterator<T> for StaticVec<T> { impl<T> FromIterator<T> for StaticVec<T> {
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self { fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
let mut vec = StaticVec::new(); let mut vec = StaticVec::new();
@ -95,6 +87,16 @@ impl<T> FromIterator<T> for StaticVec<T> {
} }
} }
impl<T> Default for StaticVec<T> {
fn default() -> Self {
Self {
len: 0,
list: unsafe_zeroed(),
more: Vec::new(),
}
}
}
impl<T> StaticVec<T> { impl<T> StaticVec<T> {
/// Create a new `StaticVec`. /// Create a new `StaticVec`.
pub fn new() -> Self { pub fn new() -> Self {
@ -105,7 +107,7 @@ impl<T> StaticVec<T> {
if self.len == self.list.len() { if self.len == self.list.len() {
// Move the fixed list to the Vec // Move the fixed list to the Vec
for x in 0..self.list.len() { 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 self.more
.push(mem::replace(self.list.get_mut(x).unwrap(), def_val)); .push(mem::replace(self.list.get_mut(x).unwrap(), def_val));
} }
@ -126,7 +128,7 @@ impl<T> StaticVec<T> {
let result = if self.len <= 0 { let result = if self.len <= 0 {
panic!("nothing to pop!") panic!("nothing to pop!")
} else if self.len <= self.list.len() { } 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) mem::replace(self.list.get_mut(self.len - 1).unwrap(), def_val)
} else { } else {
let r = self.more.pop().unwrap(); let r = self.more.pop().unwrap();