From 3408086240b2f9aeff29b5440766fab9cf87118f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 22 May 2020 13:08:57 +0800 Subject: [PATCH] Copy values differently. --- src/engine.rs | 42 +++++++++++++++++++++++------------------- src/unsafe.rs | 12 ++++++++++++ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 8ebcfe1d..2fa8348f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,7 +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, Stmt, AST}; -use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; +use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::Position; @@ -655,27 +655,31 @@ impl Engine { .or_else(|| self.packages.get_fn(hashes.0)) { // Calling pure function in method-call? - let backup: Option = if func.is_pure() && is_ref && args.len() > 0 { - // Backup the original value. It'll be consumed because the function + let mut this_copy: Option; + let mut this_pointer: Option<&mut Dynamic> = None; + + if func.is_pure() && is_ref && args.len() > 0 { + // Clone the original value. It'll be consumed because the function // is pure and doesn't know that the first value is a reference (i.e. `is_ref`) - Some(args[0].clone()) - } else { - None - }; + this_copy = Some(args[0].clone()); + + // Replace the first reference with a reference to the clone, force-casting the lifetime. + // Keep the original reference. Must remember to restore it before existing this function. + this_pointer = Some(mem::replace( + args.get_mut(0).unwrap(), + unsafe_mut_cast_to_lifetime(this_copy.as_mut().unwrap()), + )); + } // Run external function - let result = match func.get_native_fn()(args) { - Ok(r) => { - // Restore the backup value for the first argument since it has been consumed! - if let Some(backup) = backup { - *args[0] = backup; - } - r - } - Err(err) => { - return Err(err.new_position(pos)); - } - }; + let result = func.get_native_fn()(args); + + // Restore the original reference + if let Some(this_pointer) = this_pointer { + mem::replace(args.get_mut(0).unwrap(), this_pointer); + } + + let result = result.map_err(|err| err.new_position(pos))?; // See if the function match print/debug (which requires special processing) return Ok(match fn_name { diff --git a/src/unsafe.rs b/src/unsafe.rs index 46d91198..1d1ac545 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -42,6 +42,18 @@ pub fn unsafe_cast_box(item: Box) -> Result, B } } +/// # DANGEROUS!!! +/// +/// A dangerous function that blindly casts a reference from one lifetime to another lifetime. +/// +/// Force-casting a a reference to another lifetime saves on allocations and string cloning, +/// but must be used with the utmost care. +pub fn unsafe_mut_cast_to_lifetime<'a, T>(value: &mut T) -> &'a mut T { + unsafe { mem::transmute::<_, &'a mut T>(value) } +} + +/// # DANGEROUS!!! +/// /// 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