Use RestoreOnDrop.

This commit is contained in:
Stephen Chung
2022-11-08 11:52:46 +08:00
parent cba914db95
commit f4e2901353
9 changed files with 618 additions and 699 deletions

View File

@@ -9,6 +9,7 @@ use crate::engine::{
};
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
use crate::tokenizer::{is_valid_function_name, Token};
use crate::types::RestoreOnDrop;
use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Module,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
@@ -88,9 +89,11 @@ impl<'a> ArgBackup<'a> {
/// 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.
#[inline(always)]
pub fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) {
pub fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) {
if let Some(p) = self.orig_mut.take() {
args[0] = p;
} else {
unreachable!("`Some`");
}
}
}
@@ -327,7 +330,7 @@ impl Engine {
name: &str,
op_token: Option<&Token>,
hash: u64,
args: &mut FnCallArgs,
mut args: &mut FnCallArgs,
is_ref_mut: bool,
pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> {
@@ -354,48 +357,45 @@ impl Engine {
#[cfg(feature = "debugging")]
let orig_call_stack_len = global.debugger.call_stack().len();
let mut _result = if let Some(FnResolutionCacheEntry { func, source }) = func {
assert!(func.is_native());
let FnResolutionCacheEntry { func, source } = func.unwrap();
assert!(func.is_native());
let mut backup = ArgBackup::new();
let backup = &mut ArgBackup::new();
// Calling pure function but the first argument is a reference?
if is_ref_mut && func.is_pure() && !args.is_empty() {
// Clone the first argument
backup.change_first_arg_to_copy(args);
}
// Calling pure function but the first argument is a reference?
let swap = is_ref_mut && func.is_pure() && !args.is_empty();
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.push_call_stack_frame(
self.get_interned_string(name),
args.iter().map(|v| (*v).clone()).collect(),
source.clone().or_else(|| global.source.clone()),
pos,
);
}
if swap {
// Clone the first argument
backup.change_first_arg_to_copy(args);
}
// Run external function
let src = source.as_ref().map(|s| s.as_str());
let context = (self, name, src, &*global, lib, pos, level).into();
let args =
&mut *RestoreOnDrop::new_if(swap, &mut args, move |a| backup.restore_first_arg(a));
let result = if func.is_plugin_fn() {
let f = func.get_plugin_fn().unwrap();
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else {
f.call(context, args)
}
#[cfg(feature = "debugging")]
if self.debugger.is_some() {
global.debugger.push_call_stack_frame(
self.get_interned_string(name),
args.iter().map(|v| (*v).clone()).collect(),
source.clone().or_else(|| global.source.clone()),
pos,
);
}
// Run external function
let src = source.as_ref().map(|s| s.as_str());
let context = (self, name, src, &*global, lib, pos, level).into();
let mut _result = if func.is_plugin_fn() {
let f = func.get_plugin_fn().unwrap();
if !f.is_pure() && !args.is_empty() && args[0].is_read_only() {
Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into())
} else {
func.get_native_fn().unwrap()(context, args)
};
// Restore the original reference
backup.restore_first_arg(args);
result
f.call(context, args)
}
} else {
unreachable!("`Some`");
func.get_native_fn().unwrap()(context, args)
};
#[cfg(feature = "debugging")]
@@ -413,11 +413,11 @@ impl Engine {
Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r),
Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err),
};
match self
if let Err(err) = self
.run_debugger_raw(global, caches, lib, level, scope, &mut None, node, event)
{
Ok(_) => (),
Err(err) => _result = Err(err),
_result = Err(err);
}
}
@@ -543,7 +543,7 @@ impl Engine {
fn_name: &str,
op_token: Option<&Token>,
hashes: FnCallHashes,
args: &mut FnCallArgs,
mut args: &mut FnCallArgs,
is_ref_mut: bool,
_is_method_call: bool,
pos: Position,
@@ -613,19 +613,11 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
if !hashes.is_native_only() {
// Script-defined function call?
let hash = hashes.script();
let local_entry = &mut None;
if let Some(FnResolutionCacheEntry { func, ref source }) = self
.resolve_fn(
global,
caches,
local_entry,
lib,
None,
hashes.script(),
None,
false,
)
.resolve_fn(global, caches, local_entry, lib, None, hash, None, false)
.cloned()
{
// Script function call
@@ -647,8 +639,9 @@ impl Engine {
};
let orig_source = mem::replace(&mut global.source, source.clone());
let global = &mut *RestoreOnDrop::new(global, move |g| g.source = orig_source);
let result = if _is_method_call {
return if _is_method_call {
// Method call of script function - map first argument to `this`
let (first_arg, rest_args) = args.split_first_mut().unwrap();
@@ -666,27 +659,24 @@ impl Engine {
)
} else {
// Normal call of script function
let mut backup = ArgBackup::new();
let backup = &mut ArgBackup::new();
// The first argument is a reference?
if is_ref_mut && !args.is_empty() {
let swap = is_ref_mut && !args.is_empty();
if swap {
backup.change_first_arg_to_copy(args);
}
let result = self.call_script_fn(
let args = &mut *RestoreOnDrop::new_if(swap, &mut args, move |a| {
backup.restore_first_arg(a)
});
self.call_script_fn(
global, caches, lib, level, scope, &mut None, func, args, true, pos,
);
// Restore the original reference
backup.restore_first_arg(args);
result
};
// Restore the original source
global.source = orig_source;
return result.map(|r| (r, false));
)
}
.map(|r| (r, false));
}
}
@@ -722,17 +712,14 @@ impl Engine {
// Do not match function exit for arguments
#[cfg(feature = "debugging")]
let reset_debugger = global.debugger.clear_status_if(|status| {
let reset = global.debugger.clear_status_if(|status| {
matches!(status, crate::eval::DebuggerStatus::FunctionExit(..))
});
let result = self.eval_expr(global, caches, lib, level, scope, this_ptr, arg_expr);
// Restore function exit status
#[cfg(feature = "debugging")]
global.debugger.reset_status(reset_debugger);
let global = &mut *RestoreOnDrop::new(global, move |g| g.debugger.reset_status(reset));
result.map(|r| (r, arg_expr.start_position()))
self.eval_expr(global, caches, lib, level, scope, this_ptr, arg_expr)
.map(|r| (r, arg_expr.start_position()))
}
/// Call a dot method.
@@ -1389,15 +1376,13 @@ impl Engine {
Some(f) if f.is_script() => {
let fn_def = f.get_script_fn_def().expect("script-defined function");
let new_scope = &mut Scope::new();
let orig_source = mem::replace(&mut global.source, module.id_raw().cloned());
let global = &mut *RestoreOnDrop::new(global, move |g| g.source = orig_source);
let result = self.call_script_fn(
self.call_script_fn(
global, caches, lib, level, new_scope, &mut None, fn_def, &mut args, true, pos,
);
global.source = orig_source;
result
)
}
Some(f) if f.is_plugin_fn() => {