From ac05f0a0a8886051c0ededf2ad23cbe548105dc1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 14 Oct 2022 18:31:40 +0800 Subject: [PATCH] Check if function calls cannot be scripted. --- src/ast/expr.rs | 11 +++- src/eval/expr.rs | 11 +++- src/func/call.rs | 152 ++++++++++++++++++++++++--------------------- src/func/native.rs | 1 + src/optimizer.rs | 29 ++++----- src/parser.rs | 40 +++++++++--- 6 files changed, 142 insertions(+), 102 deletions(-) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index fed8d861..4adaaecf 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -181,7 +181,7 @@ impl FnCallHashes { /// _(internals)_ A function call. /// Exported under the `internals` feature only. -#[derive(Clone, Default, Hash)] +#[derive(Clone, Hash)] pub struct FnCallExpr { /// Namespace of the function, if any. #[cfg(not(feature = "no_module"))] @@ -196,6 +196,9 @@ pub struct FnCallExpr { pub capture_parent_scope: bool, /// Is this function call a native operator? pub operator_token: Option, + /// Can this function call be a scripted function? + #[cfg(not(feature = "no_function"))] + pub can_be_script: bool, /// [Position] of the function name. pub pos: Position, } @@ -215,6 +218,10 @@ impl fmt::Debug for FnCallExpr { if let Some(ref token) = self.operator_token { ff.field("operator_token", token); } + #[cfg(not(feature = "no_function"))] + if self.can_be_script { + ff.field("can_be_script", &self.can_be_script); + } ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); @@ -684,6 +691,8 @@ impl Expr { args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), capture_parent_scope: false, operator_token: None, + #[cfg(not(feature = "no_function"))] + can_be_script: true, pos, } .into(), diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 72915161..c09b48a3 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -223,9 +223,16 @@ impl Engine { hashes, args, operator_token, + #[cfg(not(feature = "no_function"))] + can_be_script, .. } = expr; + #[cfg(not(feature = "no_function"))] + let native = !can_be_script; + #[cfg(feature = "no_function")] + let native = true; + // Short-circuit native binary operator call if under Fast Operators mode if operator_token.is_some() && self.fast_operators() && args.len() == 2 { let mut lhs = self @@ -251,7 +258,8 @@ impl Engine { return self .exec_fn_call( - None, global, caches, lib, name, *hashes, operands, false, false, pos, level, + None, global, caches, lib, name, native, *hashes, operands, false, false, pos, + level, ) .map(|(v, ..)| v); } @@ -280,6 +288,7 @@ impl Engine { lib, this_ptr, name, + native, first_arg, args, *hashes, diff --git a/src/func/call.rs b/src/func/call.rs index 64d8958c..c3ca7511 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -578,6 +578,7 @@ impl Engine { caches: &mut Caches, lib: &[&Module], fn_name: &str, + _native_only: bool, hashes: FnCallHashes, args: &mut FnCallArgs, is_ref_mut: bool, @@ -644,89 +645,90 @@ impl Engine { let level = level + 1; - // Script-defined function call? #[cfg(not(feature = "no_function"))] - let local_entry = &mut None; + if !_native_only { + // Script-defined function call? + let local_entry = &mut None; - #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, ref source }) = self - .resolve_fn( - global, - caches, - local_entry, - lib, - fn_name, - hashes.script, - None, - false, - None, - ) - .cloned() - { - // Script function call - assert!(func.is_script()); - - let func = func.get_script_fn_def().expect("script-defined function"); - - if func.body.is_empty() { - return Ok((Dynamic::UNIT, false)); - } - - let mut empty_scope; - let scope = match _scope { - Some(scope) => scope, - None => { - empty_scope = Scope::new(); - &mut empty_scope - } - }; - - let orig_source = mem::replace( - &mut global.source, - source - .as_ref() - .map_or(crate::Identifier::new_const(), |s| (**s).clone()), - ); - - let result = if _is_method_call { - // Method call of script function - map first argument to `this` - let (first_arg, rest_args) = args.split_first_mut().unwrap(); - - self.call_script_fn( - scope, + if let Some(FnResolutionCacheEntry { func, ref source }) = self + .resolve_fn( global, caches, + local_entry, lib, - &mut Some(*first_arg), - func, - rest_args, - true, - pos, - level, + fn_name, + hashes.script, + None, + false, + None, ) - } else { - // Normal call of script function - let mut backup = ArgBackup::new(); + .cloned() + { + // Script function call + assert!(func.is_script()); - // The first argument is a reference? - if is_ref_mut && !args.is_empty() { - backup.change_first_arg_to_copy(args); + let func = func.get_script_fn_def().expect("script-defined function"); + + if func.body.is_empty() { + return Ok((Dynamic::UNIT, false)); } - let result = self.call_script_fn( - scope, global, caches, lib, &mut None, func, args, true, pos, level, + let mut empty_scope; + let scope = match _scope { + Some(scope) => scope, + None => { + empty_scope = Scope::new(); + &mut empty_scope + } + }; + + let orig_source = mem::replace( + &mut global.source, + source + .as_ref() + .map_or(crate::Identifier::new_const(), |s| (**s).clone()), ); - // Restore the original reference - backup.restore_first_arg(args); + let result = if _is_method_call { + // Method call of script function - map first argument to `this` + let (first_arg, rest_args) = args.split_first_mut().unwrap(); - result - }; + self.call_script_fn( + scope, + global, + caches, + lib, + &mut Some(*first_arg), + func, + rest_args, + true, + pos, + level, + ) + } else { + // Normal call of script function + let mut backup = ArgBackup::new(); - // Restore the original source - global.source = orig_source; + // The first argument is a reference? + if is_ref_mut && !args.is_empty() { + backup.change_first_arg_to_copy(args); + } - return Ok((result?, false)); + let result = self.call_script_fn( + scope, global, caches, lib, &mut None, func, args, true, pos, level, + ); + + // Restore the original reference + backup.restore_first_arg(args); + + result + }; + + // Restore the original source + global.source = orig_source; + + return Ok((result?, false)); + } } // Native function call @@ -836,6 +838,7 @@ impl Engine { caches, lib, fn_name, + false, new_hash, &mut args, false, @@ -881,6 +884,7 @@ impl Engine { caches, lib, fn_name, + false, new_hash, &mut args, is_ref_mut, @@ -968,6 +972,7 @@ impl Engine { caches, lib, fn_name, + false, hash, &mut args, is_ref_mut, @@ -995,6 +1000,7 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, + native_only: bool, first_arg: Option<&Expr>, args_expr: &[Expr], hashes: FnCallHashes, @@ -1003,6 +1009,7 @@ impl Engine { pos: Position, level: usize, ) -> RhaiResult { + let native = native_only; let mut first_arg = first_arg; let mut a_expr = args_expr; let mut total_args = if first_arg.is_some() { 1 } else { 0 } + a_expr.len(); @@ -1199,8 +1206,8 @@ impl Engine { return self .exec_fn_call( - scope, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, - level, + scope, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false, + pos, level, ) .map(|(v, ..)| v); } @@ -1262,7 +1269,8 @@ impl Engine { } self.exec_fn_call( - None, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, level, + None, global, caches, lib, name, native, hashes, &mut args, is_ref_mut, false, pos, + level, ) .map(|(v, ..)| v) } diff --git a/src/func/native.rs b/src/func/native.rs index 34cd6ec3..b3681e74 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -422,6 +422,7 @@ impl<'a> NativeCallContext<'a> { caches, self.lib, fn_name, + false, hash, args, is_ref_mut, diff --git a/src/optimizer.rs b/src/optimizer.rs index a0f187c9..dea68ea3 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -897,24 +897,17 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b Stmt::Expr(expr) => { optimize_expr(expr, state, false); - match &mut **expr { - // func(...) - Expr::FnCall(x, pos) => { - state.set_dirty(); - *stmt = Stmt::FnCall(mem::take(x), *pos); - } - // {...}; - Expr::Stmt(x) => { - if x.is_empty() { - state.set_dirty(); - *stmt = Stmt::Noop(x.position()); - } else { - state.set_dirty(); - *stmt = mem::take(&mut **x).into(); - } - } - // expr; - _ => (), + if matches!(**expr, Expr::FnCall(..) | Expr::Stmt(..)) { + state.set_dirty(); + *stmt = match *mem::take(expr) { + // func(...); + Expr::FnCall(x, pos) => Stmt::FnCall(x, pos), + // {}; + Expr::Stmt(x) if x.is_empty() => Stmt::Noop(x.position()), + // {...}; + Expr::Stmt(x) => (*x).into(), + _ => unreachable!(), + }; } } diff --git a/src/parser.rs b/src/parser.rs index 23f96d7b..93094aed 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -193,7 +193,6 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_function"))] let is_func_name = _lib.values().any(|f| f.name == name); - #[cfg(feature = "no_function")] let is_func_name = false; @@ -600,7 +599,9 @@ impl Engine { #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, 0); - let hashes = if is_valid_function_name(&id) { + let is_valid_function_name = is_valid_function_name(&id); + + let hashes = if is_valid_function_name { hash.into() } else { FnCallHashes::from_native(hash) @@ -612,6 +613,8 @@ impl Engine { name: state.get_interned_string(id), capture_parent_scope, operator_token: None, + #[cfg(not(feature = "no_function"))] + can_be_script: is_valid_function_name, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -668,7 +671,9 @@ impl Engine { #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, args.len()); - let hashes = if is_valid_function_name(&id) { + let is_valid_function_name = is_valid_function_name(&id); + + let hashes = if is_valid_function_name { hash.into() } else { FnCallHashes::from_native(hash) @@ -680,6 +685,8 @@ impl Engine { name: state.get_interned_string(id), capture_parent_scope, operator_token: None, + #[cfg(not(feature = "no_function"))] + can_be_script: is_valid_function_name, #[cfg(not(feature = "no_module"))] namespace, hashes, @@ -1912,12 +1919,14 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { + namespace: Default::default(), name: state.get_interned_string("-"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), args, pos, operator_token: Some(token), - ..Default::default() + capture_parent_scope: false, + can_be_script: false, } .into_fn_call_expr(pos)) } @@ -1940,12 +1949,14 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { + namespace: Default::default(), name: state.get_interned_string("+"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), args, pos, operator_token: Some(token), - ..Default::default() + capture_parent_scope: false, + can_be_script: false, } .into_fn_call_expr(pos)) } @@ -1961,12 +1972,14 @@ impl Engine { args.shrink_to_fit(); Ok(FnCallExpr { + namespace: Default::default(), name: state.get_interned_string("!"), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), args, pos, operator_token: Some(token), - ..Default::default() + capture_parent_scope: false, + can_be_script: false, } .into_fn_call_expr(pos)) } @@ -2335,18 +2348,22 @@ impl Engine { let op = op_token.syntax(); let hash = calc_fn_hash(None, &op, 2); - let operator_token = if is_valid_function_name(&op) { + let is_function = is_valid_function_name(&op); + let operator_token = if is_function { None } else { Some(op_token.clone()) }; let op_base = FnCallExpr { + namespace: Default::default(), name: state.get_interned_string(op.as_ref()), hashes: FnCallHashes::from_native(hash), + args: StaticVec::new_const(), pos, operator_token, - ..Default::default() + capture_parent_scope: false, + can_be_script: is_function, }; let mut args = StaticVec::new_const(); @@ -2432,7 +2449,7 @@ impl Engine { let pos = args[0].start_position(); FnCallExpr { - hashes: if is_valid_function_name(&s) { + hashes: if is_function { hash.into() } else { FnCallHashes::from_native(hash) @@ -3659,6 +3676,7 @@ impl Engine { ); let expr = FnCallExpr { + namespace: Default::default(), name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native(calc_fn_hash( None, @@ -3667,7 +3685,9 @@ impl Engine { )), args, pos, - ..Default::default() + operator_token: None, + capture_parent_scope: false, + can_be_script: false, } .into_fn_call_expr(pos);