Simplify function calling.

This commit is contained in:
Stephen Chung 2020-07-31 12:11:16 +08:00
parent a7ff207636
commit cb005506e2
4 changed files with 130 additions and 99 deletions

View File

@ -1179,7 +1179,6 @@ impl Engine {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value)) => { Dynamic(Union::Array(mut rhs_value)) => {
let op = "=="; let op = "==";
let mut scope = Scope::new();
// Call the `==` operator to compare each value // Call the `==` operator to compare each value
for value in rhs_value.iter_mut() { for value in rhs_value.iter_mut() {
@ -1190,13 +1189,13 @@ impl Engine {
let hash = let hash =
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id())); calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
let (r, _) = self if self
.call_fn_raw( .call_native_fn(state, lib, op, hash, args, false, false, def_value)
&mut scope, mods, state, lib, op, hash, args, false, false, false, .map_err(|err| err.new_position(rhs.position()))?
def_value, level, .0
) .as_bool()
.map_err(|err| err.new_position(rhs.position()))?; .unwrap_or(false)
if r.as_bool().unwrap_or(false) { {
return Ok(true.into()); return Ok(true.into());
} }
} }

View File

@ -41,7 +41,7 @@ use crate::stdlib::{
format, format,
iter::{empty, once}, iter::{empty, once},
mem, mem,
string::ToString, string::{String, ToString},
vec::Vec, vec::Vec,
}; };
@ -67,43 +67,69 @@ fn extract_prop_from_setter(_fn_name: &str) -> Option<&str> {
None None
} }
/// This function replaces the first argument of a method call with a clone copy. /// A type that temporarily stores a mutable reference to a `Dynamic`,
/// This is to prevent a pure function unintentionally consuming the first argument. /// replacing it with a cloned copy.
fn normalize_first_arg<'a>( #[derive(Debug, Default)]
normalize: bool, struct ArgBackup<'a> {
this_copy: &mut Dynamic, orig_mut: Option<&'a mut Dynamic>,
old_this_ptr: &mut Option<&'a mut Dynamic>, value_copy: Dynamic,
args: &mut FnCallArgs<'a>, }
) {
impl<'a> ArgBackup<'a> {
/// This function replaces the first argument of a method call with a clone copy.
/// This is to prevent a pure function unintentionally consuming the first argument.
///
/// `restore_first_arg` must be called before the end of the scope to prevent the shorter lifetime from leaking.
///
/// # Safety
///
/// This method blindly casts a reference to another lifetime, which saves allocation and string cloning.
///
/// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak.
fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) {
// Only do it for method calls with arguments. // Only do it for method calls with arguments.
if !normalize || args.is_empty() { if !normalize || args.is_empty() {
return; return;
} }
// Clone the original value. // Clone the original value.
*this_copy = args[0].clone(); self.value_copy = args[0].clone();
// Replace the first reference with a reference to the clone, force-casting the lifetime. // Replace the first reference with a reference to the clone, force-casting the lifetime.
// Keep the original reference. Must remember to restore it later with `restore_first_arg_of_method_call`. // Must remember to restore it later with `restore_first_arg`.
// //
// # Safety // # Safety
// //
// Blindly casting a a reference to another lifetime saves on allocations and string cloning, // Blindly casting a reference to another lifetime saves allocation and string cloning,
// but must be used with the utmost care. // but must be used with the utmost care.
// //
// We can do this here because, at the end of this scope, we'd restore the original reference // We can do this here because, before the end of this scope, we'd restore the original reference
// with `restore_first_arg_of_method_call`. Therefore this shorter lifetime does not get "out". // via `restore_first_arg`. Therefore this shorter lifetime does not leak.
let this_pointer = mem::replace(args.get_mut(0).unwrap(), unsafe { self.orig_mut = Some(mem::replace(args.get_mut(0).unwrap(), unsafe {
mem::transmute(this_copy) mem::transmute(&mut self.value_copy)
}); }));
}
*old_this_ptr = Some(this_pointer); /// This function restores the first argument that was replaced by `change_first_arg_to_copy`.
///
/// # Safety
///
/// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting
/// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) {
if let Some(this_pointer) = self.orig_mut.take() {
args[0] = this_pointer;
}
}
} }
/// This function restores the first argument that was replaced by `normalize_first_arg_of_method_call`. impl Drop for ArgBackup<'_> {
fn restore_first_arg<'a>(old_this_ptr: Option<&'a mut Dynamic>, args: &mut FnCallArgs<'a>) { fn drop(&mut self) {
if let Some(this_pointer) = old_this_ptr { // Panic if the shorter lifetime leaks.
args[0] = this_pointer; assert!(
self.orig_mut.is_none(),
"MutBackup::restore has not been called prior to existing this scope"
);
} }
} }
@ -138,34 +164,21 @@ impl Engine {
/// Function call arguments be _consumed_ when the function requires them to be passed by value. /// Function call arguments be _consumed_ when the function requires them to be passed by value.
/// All function arguments not in the first position are always passed by value and thus consumed. /// All function arguments not in the first position are always passed by value and thus consumed.
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_fn_raw( pub(crate) fn call_native_fn(
&self, &self,
_scope: &mut Scope,
_mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
fn_name: &str, fn_name: &str,
hash_fn: u64, hash_fn: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref: bool, is_ref: bool,
_is_method: bool,
pub_only: bool, pub_only: bool,
def_val: Option<bool>, def_val: Option<bool>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> { ) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state)?; self.inc_operations(state)?;
// Check for stack overflow // Search for the native function
#[cfg(not(feature = "no_function"))] // First search registered functions (can override packages)
#[cfg(not(feature = "unchecked"))]
if _level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
// Search for the function
// First search registered native functions (can override packages)
// Then search packages // Then search packages
let func = self let func = self
.global_module .global_module
@ -173,22 +186,19 @@ impl Engine {
.or_else(|| self.packages.get_fn(hash_fn, pub_only)); .or_else(|| self.packages.get_fn(hash_fn, pub_only));
if let Some(func) = func { if let Some(func) = func {
#[cfg(not(feature = "no_function"))] assert!(func.is_native());
let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !_is_method));
#[cfg(feature = "no_function")]
let need_normalize = is_ref && func.is_pure();
let mut this_copy: Dynamic = Default::default();
let mut old_this_ptr: Option<&mut Dynamic> = None;
// Calling pure function but the first argument is a reference? // Calling pure function but the first argument is a reference?
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args); let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref && func.is_pure(), args);
// Run external function // Run external function
let result = func.get_native_fn()(self, lib, args)?; let result = func.get_native_fn()(self, lib, args);
// Restore the original reference // Restore the original reference
restore_first_arg(old_this_ptr, args); backup.restore_first_arg(args);
let result = result?;
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match fn_name { return Ok(match fn_name {
@ -320,6 +330,17 @@ impl Engine {
args: &mut FnCallArgs, args: &mut FnCallArgs,
level: usize, level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.inc_operations(state)?;
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
let orig_scope_level = state.scope_level; let orig_scope_level = state.scope_level;
state.scope_level += 1; state.scope_level += 1;
@ -382,7 +403,7 @@ impl Engine {
|| self.packages.contains_fn(hash_fn, pub_only) || self.packages.contains_fn(hash_fn, pub_only)
} }
/// Perform an actual function call, taking care of special functions /// Perform an actual function call, native Rust or scripted, taking care of special functions.
/// Position in `EvalAltResult` is `None` and must be set afterwards. /// Position in `EvalAltResult` is `None` and must be set afterwards.
/// ///
/// ## WARNING /// ## WARNING
@ -470,33 +491,26 @@ impl Engine {
)? )?
} else { } else {
// Normal call of script function - map first argument to `this` // Normal call of script function - map first argument to `this`
let mut first_copy: Dynamic = Default::default();
let mut old_first: Option<&mut Dynamic> = None;
// The first argument is a reference? // The first argument is a reference?
normalize_first_arg(is_ref, &mut first_copy, &mut old_first, args); let mut backup: ArgBackup = Default::default();
backup.change_first_arg_to_copy(is_ref, args);
let result = self.call_script_fn( let result = self.call_script_fn(
scope, mods, state, lib, &mut None, fn_name, func, args, level, scope, mods, state, lib, &mut None, fn_name, func, args, level,
)?; );
// Restore the original reference // Restore the original reference
restore_first_arg(old_first, args); backup.restore_first_arg(args);
result result?
}; };
Ok((result, false)) Ok((result, false))
} }
// Normal native function call // Normal native function call
_ => { _ => self.call_native_fn(
let mut scope = Scope::new(); state, lib, fn_name, hash_fn, args, is_ref, pub_only, def_val,
let mut mods = Imports::new(); ),
self.call_fn_raw(
&mut scope, &mut mods, state, lib, fn_name, hash_fn, args, is_ref, is_method,
pub_only, def_val, level,
)
}
} }
} }
@ -508,9 +522,21 @@ impl Engine {
mods: &mut Imports, mods: &mut Imports,
state: &mut State, state: &mut State,
lib: &Module, lib: &Module,
script: &Dynamic, script_expr: &Dynamic,
level: usize,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
let script = script.as_str().map_err(|typ| { self.inc_operations(state)?;
// Check for stack overflow
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "unchecked"))]
if level > self.limits.max_call_stack_depth {
return Err(Box::new(
EvalAltResult::ErrorStackOverflow(Position::none()),
));
}
let script = script_expr.as_str().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType( EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(), self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(), typ.into(),
@ -782,12 +808,12 @@ impl Engine {
let expr = args_expr.get(0).unwrap(); let expr = args_expr.get(0).unwrap();
let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let script = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let result = self let result = self
.eval_script_expr(scope, mods, state, lib, &script) .eval_script_expr(scope, mods, state, lib, &script, level + 1)
.map_err(|err| err.new_position(expr.position())); .map_err(|err| err.new_position(expr.position()));
if scope.len() != prev_len {
// IMPORTANT! If the eval defines new variables in the current scope, // IMPORTANT! If the eval defines new variables in the current scope,
// all variable offsets from this point on will be mis-aligned. // all variable offsets from this point on will be mis-aligned.
if scope.len() != prev_len {
state.always_search = true; state.always_search = true;
} }

View File

@ -280,6 +280,16 @@ impl CallableFunction {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
} }
} }
/// Is this a native Rust function?
pub fn is_native(&self) -> bool {
match self {
Self::Pure(_) | Self::Method(_) => true,
Self::Iterator(_) => true,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Get the access mode. /// Get the access mode.
pub fn access(&self) -> FnAccess { pub fn access(&self) -> FnAccess {
match self { match self {

View File

@ -3,7 +3,7 @@
use crate::any::Dynamic; use crate::any::Dynamic;
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{ use crate::engine::{
Engine, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF, Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::fn_native::FnPtr; use crate::fn_native::FnPtr;
use crate::module::Module; use crate::module::Module;
@ -132,22 +132,18 @@ fn call_fn_with_constant_arguments(
state state
.engine .engine
.call_fn_raw( .call_native_fn(
&mut Scope::new(),
&mut Imports::new(),
&mut Default::default(), &mut Default::default(),
state.lib, state.lib,
fn_name, fn_name,
hash_fn, hash_fn,
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(), arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false, false,
false,
true, true,
None, None,
0,
) )
.map(|(v, _)| Some(v)) .ok()
.unwrap_or_else(|_| None) .map(|(v, _)| v)
} }
/// Optimize a statement. /// Optimize a statement.