diff --git a/src/api/limits.rs b/src/api/limits.rs index 32daf13e..5191f2e9 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -102,7 +102,7 @@ impl Default for Limits { impl Engine { /// Is there a data size limit set? - #[inline] + #[inline(always)] pub(crate) const fn has_data_size_limit(&self) -> bool { self.limits.max_string_size.is_some() || { diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 1d39171d..af718685 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -624,6 +624,38 @@ impl Expr { _ => None, } } + /// Get the [options][ASTFlags] of the expression. + #[inline] + #[must_use] + pub const fn options(&self) -> ASTFlags { + match self { + Self::Index(_, options, _) | Self::Dot(_, options, _) => *options, + + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(..) => ASTFlags::NONE, + + Self::DynamicConstant(..) + | Self::BoolConstant(..) + | Self::IntegerConstant(..) + | Self::CharConstant(..) + | Self::Unit(..) + | Self::StringConstant(..) + | Self::Array(..) + | Self::Map(..) + | Self::Variable(..) + | Self::And(..) + | Self::Or(..) + | Self::Coalesce(..) + | Self::FnCall(..) + | Self::MethodCall(..) + | Self::InterpolatedString(..) + | Self::Property(..) + | Self::Stmt(..) => ASTFlags::NONE, + + #[cfg(not(feature = "no_custom_syntax"))] + Self::Custom(..) => ASTFlags::NONE, + } + } /// Get the [position][Position] of the expression. #[inline] #[must_use] diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 541332c3..2fd3df49 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -656,6 +656,34 @@ impl Stmt { pub const fn is_noop(&self) -> bool { matches!(self, Self::Noop(..)) } + /// Get the [options][ASTFlags] of this statement. + #[inline] + #[must_use] + pub const fn options(&self) -> ASTFlags { + match self { + Self::Do(_, options, _) + | Self::Var(_, options, _) + | Self::BreakLoop(_, options, _) + | Self::Return(_, options, _) => *options, + + Self::Noop(..) + | Self::If(..) + | Self::Switch(..) + | Self::Block(..) + | Self::Expr(..) + | Self::FnCall(..) + | Self::While(..) + | Self::For(..) + | Self::TryCatch(..) + | Self::Assignment(..) => ASTFlags::NONE, + + #[cfg(not(feature = "no_module"))] + Self::Import(..) | Self::Export(..) => ASTFlags::NONE, + + #[cfg(not(feature = "no_closure"))] + Self::Share(..) => ASTFlags::NONE, + } + } /// Get the [position][Position] of this statement. #[must_use] pub fn position(&self) -> Position { diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 5c3fa792..d0026f33 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -22,7 +22,7 @@ pub enum ChainType { } impl From<&Expr> for ChainType { - #[inline] + #[inline(always)] fn from(expr: &Expr) -> Self { match expr { #[cfg(not(feature = "no_index"))] @@ -36,31 +36,29 @@ impl From<&Expr> for ChainType { impl Engine { /// Chain-evaluate a dot/index chain. - /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] may be [`NONE`][Position::NONE] and should be set afterwards. fn eval_dot_index_chain_helper( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, this_ptr: &mut Dynamic, + root: &Expr, + parent: &Expr, target: &mut Target, - root: (&str, Position), - _parent: &Expr, - parent_options: ASTFlags, - rhs: &Expr, idx_values: &mut FnArgsVec, - chain_type: ChainType, + rhs: &Expr, new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); + let op_pos = parent.position(); #[cfg(feature = "debugging")] let scope = &mut Scope::new(); - match chain_type { + match ChainType::from(parent) { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { // Check for existence with the null conditional operator - if parent_options.contains(ASTFlags::NEGATED) && target.is_unit() { + if parent.options().contains(ASTFlags::NEGATED) && target.is_unit() { return Ok((Dynamic::UNIT, false)); } @@ -68,33 +66,32 @@ impl Engine { match rhs { // xxx[idx].expr... | xxx[idx][expr]... - Expr::Dot(x, options, x_pos) | Expr::Index(x, options, x_pos) - if !parent_options.contains(ASTFlags::BREAK) => + Expr::Dot(x, ..) | Expr::Index(x, ..) + if !parent.options().contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] - self.run_debugger(global, caches, scope, this_ptr, _parent)?; + self.run_debugger(global, caches, scope, this_ptr, parent)?; let idx_val = &mut idx_values.pop().unwrap(); let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); - let rhs_chain = rhs.into(); let (try_setter, result) = { let mut obj = self.get_indexed_mut( - global, caches, target, idx_val, idx_pos, false, true, + global, caches, target, idx_val, idx_pos, op_pos, false, true, )?; let is_obj_temp_val = obj.is_temp_value(); let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - global, caches, this_ptr, obj_ptr, root, rhs, *options, &x.rhs, - idx_values, rhs_chain, new_val, + global, caches, this_ptr, root, rhs, obj_ptr, idx_values, &x.rhs, + new_val, ) { Ok((result, true)) if is_obj_temp_val => { (Some(obj.take_or_clone()), (result, true)) } Ok(result) => (None, result), - Err(err) => return Err(err.fill_position(*x_pos)), + Err(err) => return Err(err), } }; @@ -102,11 +99,13 @@ impl Engine { // Try to call index setter if value is changed let idx = &mut idx_val_for_setter; let new_val = &mut new_val; - self.call_indexer_set(global, caches, target, idx, new_val, is_ref_mut) - .or_else(|e| match *e { - ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), - _ => Err(e), - })?; + self.call_indexer_set( + global, caches, target, idx, new_val, is_ref_mut, op_pos, + ) + .or_else(|e| match *e { + ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), + _ => Err(e), + })?; } Ok(result) @@ -114,21 +113,21 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(global, caches, scope, this_ptr, _parent)?; + self.run_debugger(global, caches, scope, this_ptr, parent)?; let (new_val, op_info) = new_val.take().expect("`Some`"); let idx_val = &mut idx_values.pop().unwrap(); let idx = &mut idx_val.clone(); let try_setter = match self - .get_indexed_mut(global, caches, target, idx, pos, true, false) + .get_indexed_mut(global, caches, target, idx, pos, op_pos, true, false) { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - global, caches, op_info, obj_ptr, root, new_val, + global, caches, op_info, root, obj_ptr, new_val, )?; - self.check_data_size(obj_ptr, op_info.pos)?; + self.check_data_size(obj_ptr.as_ref(), op_info.pos)?; None } // Indexed value cannot be referenced - use indexer @@ -144,12 +143,13 @@ impl Engine { let idx = &mut idx_val.clone(); // Call the index getter to get the current value - if let Ok(val) = self.call_indexer_get(global, caches, target, idx) + if let Ok(val) = + self.call_indexer_get(global, caches, target, idx, op_pos) { let mut val = val.into(); // Run the op-assignment self.eval_op_assignment( - global, caches, op_info, &mut val, root, new_val, + global, caches, op_info, root, &mut val, new_val, )?; // Replace new value new_val = val.take_or_clone(); @@ -161,7 +161,7 @@ impl Engine { let new_val = &mut new_val; self.call_indexer_set( - global, caches, target, idx_val, new_val, is_ref_mut, + global, caches, target, idx_val, new_val, is_ref_mut, op_pos, )?; } @@ -170,12 +170,14 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(global, caches, scope, this_ptr, _parent)?; + self.run_debugger(global, caches, scope, this_ptr, parent)?; let idx_val = &mut idx_values.pop().unwrap(); - self.get_indexed_mut(global, caches, target, idx_val, pos, false, true) - .map(|v| (v.take_or_clone(), false)) + self.get_indexed_mut( + global, caches, target, idx_val, pos, op_pos, false, true, + ) + .map(|v| (v.take_or_clone(), false)) } } } @@ -183,7 +185,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] ChainType::Dotting => { // Check for existence with the Elvis operator - if parent_options.contains(ASTFlags::NEGATED) && target.is_unit() { + if parent.options().contains(ASTFlags::NEGATED) && target.is_unit() { return Ok((Dynamic::UNIT, false)); } @@ -208,10 +210,10 @@ impl Engine { &mut *RestoreOnDrop::lock(idx_values, move |v| v.truncate(offset)); let call_args = &mut idx_values[offset..]; - let pos1 = args.get(0).map_or(Position::NONE, Expr::position); + let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); self.make_method_call( - global, caches, name, *hashes, target, call_args, pos1, *pos, + global, caches, name, *hashes, target, call_args, arg1_pos, *pos, ) } // xxx.fn_name(...) = ??? @@ -231,10 +233,10 @@ impl Engine { let (new_val, op_info) = new_val.take().expect("`Some`"); { let val_target = &mut self.get_indexed_mut( - global, caches, target, index, *pos, true, false, + global, caches, target, index, *pos, op_pos, true, false, )?; self.eval_op_assignment( - global, caches, op_info, val_target, root, new_val, + global, caches, op_info, root, val_target, new_val, )?; } self.check_data_size(target.source(), op_info.pos)?; @@ -246,8 +248,9 @@ impl Engine { self.run_debugger(global, caches, scope, this_ptr, rhs)?; let index = &mut x.2.clone().into(); - let val = self - .get_indexed_mut(global, caches, target, index, *pos, false, false)?; + let val = self.get_indexed_mut( + global, caches, target, index, *pos, op_pos, false, false, + )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? @@ -268,12 +271,16 @@ impl Engine { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); - self.call_indexer_get(global, caches, target, &mut prop) - .map(|r| (r, false)) - .map_err(|e| match *e { + self.call_indexer_get( + global, caches, target, &mut prop, op_pos, + ) + .map(|r| (r, false)) + .map_err(|e| { + match *e { ERR::ErrorIndexingType(..) => err, _ => e, - }) + } + }) } _ => Err(err), })?; @@ -282,7 +289,7 @@ impl Engine { let orig_val = &mut (&mut orig_val).into(); self.eval_op_assignment( - global, caches, op_info, orig_val, root, new_val, + global, caches, op_info, root, orig_val, new_val, )?; } @@ -299,7 +306,7 @@ impl Engine { let idx = &mut name.into(); let new_val = &mut new_val; self.call_indexer_set( - global, caches, target, idx, new_val, is_ref_mut, + global, caches, target, idx, new_val, is_ref_mut, op_pos, ) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, @@ -324,7 +331,7 @@ impl Engine { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); - self.call_indexer_get(global, caches, target, &mut prop) + self.call_indexer_get(global, caches, target, &mut prop, op_pos) .map(|r| (r, false)) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, @@ -338,9 +345,7 @@ impl Engine { ) } // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr - Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) - if target.is_map() => - { + Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => { let _node = &x.lhs; let val_target = &mut match x.lhs { @@ -350,7 +355,7 @@ impl Engine { let index = &mut p.2.clone().into(); self.get_indexed_mut( - global, caches, target, index, pos, false, true, + global, caches, target, index, pos, op_pos, false, true, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr @@ -375,10 +380,10 @@ impl Engine { }); let call_args = &mut idx_values[offset..]; - let pos1 = args.get(0).map_or(Position::NONE, Expr::position); + let arg1_pos = args.get(0).map_or(Position::NONE, Expr::position); self.make_method_call( - global, caches, name, *hashes, target, call_args, pos1, pos, + global, caches, name, *hashes, target, call_args, arg1_pos, pos, )? .0 .into() @@ -390,16 +395,14 @@ impl Engine { // Others - syntax error ref expr => unreachable!("invalid dot expression: {:?}", expr), }; - let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, caches, this_ptr, val_target, root, rhs, *options, &x.rhs, - idx_values, rhs_chain, new_val, + global, caches, this_ptr, root, rhs, val_target, idx_values, &x.rhs, + new_val, ) - .map_err(|err| err.fill_position(*x_pos)) } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr - Expr::Index(x, options, x_pos) | Expr::Dot(x, options, x_pos) => { + Expr::Index(x, ..) | Expr::Dot(x, ..) => { let _node = &x.lhs; match x.lhs { @@ -409,7 +412,6 @@ impl Engine { self.run_debugger(global, caches, scope, this_ptr, _node)?; let ((getter, hash_get), (setter, hash_set), name) = &**p; - let rhs_chain = rhs.into(); let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; let args = &mut arg_values[..1]; @@ -423,24 +425,26 @@ impl Engine { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); - self.call_indexer_get(global, caches, target, &mut prop) - .map(|r| (r, false)) - .map_err(|e| match *e { + self.call_indexer_get( + global, caches, target, &mut prop, op_pos, + ) + .map(|r| (r, false)) + .map_err( + |e| match *e { ERR::ErrorIndexingType(..) => err, _ => e, - }) + }, + ) } _ => Err(err), })?; let val = &mut (&mut val).into(); - let (result, may_be_changed) = self - .eval_dot_index_chain_helper( - global, caches, this_ptr, val, root, rhs, *options, &x.rhs, - idx_values, rhs_chain, new_val, - ) - .map_err(|err| err.fill_position(*x_pos))?; + let (result, may_be_changed) = self.eval_dot_index_chain_helper( + global, caches, this_ptr, root, rhs, val, idx_values, &x.rhs, + new_val, + )?; // Feed the value back via a setter just in case it has been updated if may_be_changed { @@ -459,7 +463,7 @@ impl Engine { let new_val = val; self.call_indexer_set( global, caches, target, idx, new_val, - is_ref_mut, + is_ref_mut, op_pos, ) .or_else(|e| match *e { // If there is no setter, no need to feed it @@ -510,13 +514,11 @@ impl Engine { }; let val = &mut val.into(); - let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, caches, this_ptr, val, root, rhs, *options, &x.rhs, - idx_values, rhs_chain, new_val, + global, caches, this_ptr, root, rhs, val, idx_values, &x.rhs, + new_val, ) - .map_err(|err| err.fill_position(pos)) } // xxx.module::fn_name(...) - syntax error Expr::MethodCall(..) => unreachable!( @@ -527,7 +529,7 @@ impl Engine { } } // Syntax error - _ => Err(ERR::ErrorDotExpr("".into(), rhs.start_position()).into()), + expr => unreachable!("invalid chaining expression: {:?}", expr), } } } @@ -544,11 +546,11 @@ impl Engine { new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); - let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr { + let crate::ast::BinaryExpr { lhs, rhs } = match expr { #[cfg(not(feature = "no_index"))] - Expr::Index(x, options, pos) => (&**x, *options, *pos), + Expr::Index(x, ..) => &**x, #[cfg(not(feature = "no_object"))] - Expr::Dot(x, options, pos) => (&**x, *options, *pos), + Expr::Dot(x, ..) => &**x, expr => unreachable!("Expr::Index or Expr::Dot expected but gets {:?}", expr), }; @@ -578,48 +580,42 @@ impl Engine { // All other patterns - evaluate the arguments chain _ => { self.eval_dot_index_chain_arguments( - global, caches, scope, this_ptr, rhs, options, chain_type, idx_values, + global, caches, scope, this_ptr, expr, idx_values, rhs, )?; } } match lhs { // id.??? or id[???] - Expr::Variable(x, .., var_pos) => { + Expr::Variable(.., var_pos) => { #[cfg(feature = "debugging")] self.run_debugger(global, caches, scope, this_ptr, lhs)?; self.track_operation(global, *var_pos)?; - let (mut target, ..) = - self.search_namespace(global, caches, scope, this_ptr, lhs)?; + let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let obj_ptr = &mut target; - let root = (x.3.as_str(), *var_pos); let mut this = Dynamic::NULL; self.eval_dot_index_chain_helper( - global, caches, &mut this, obj_ptr, root, expr, options, rhs, idx_values, - chain_type, new_val, + global, caches, &mut this, lhs, expr, obj_ptr, idx_values, rhs, new_val, ) } // {expr}.??? = ??? or {expr}[???] = ??? _ if new_val.is_some() => unreachable!("cannot assign to an expression"), // {expr}.??? or {expr}[???] - expr => { + lhs_expr => { let value = self - .eval_expr(global, caches, scope, this_ptr, expr)? + .eval_expr(global, caches, scope, this_ptr, lhs_expr)? .flatten(); let obj_ptr = &mut value.into(); - let root = ("", expr.start_position()); self.eval_dot_index_chain_helper( - global, caches, this_ptr, obj_ptr, root, expr, options, rhs, idx_values, - chain_type, new_val, + global, caches, this_ptr, lhs_expr, expr, obj_ptr, idx_values, rhs, new_val, ) } } .map(|(v, ..)| v) - .map_err(|err| err.fill_position(op_pos)) } /// Evaluate a chain of indexes and store the results in a [`FnArgsVec`]. @@ -629,13 +625,14 @@ impl Engine { caches: &mut Caches, scope: &mut Scope, this_ptr: &mut Dynamic, - expr: &Expr, - parent_options: ASTFlags, - parent_chain_type: ChainType, + parent: &Expr, idx_values: &mut FnArgsVec, + expr: &Expr, ) -> RhaiResultOf<()> { self.track_operation(global, expr.position())?; + let parent_chain_type = ChainType::from(parent); + match expr { #[cfg(not(feature = "no_object"))] Expr::MethodCall(x, ..) @@ -658,8 +655,8 @@ impl Engine { Expr::Property(..) if parent_chain_type == ChainType::Dotting => (), Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), - Expr::Index(x, options, ..) | Expr::Dot(x, options, ..) - if !parent_options.contains(ASTFlags::BREAK) => + Expr::Index(x, ..) | Expr::Dot(x, ..) + if !parent.options().contains(ASTFlags::BREAK) => { let crate::ast::BinaryExpr { lhs, rhs, .. } = &**x; @@ -702,10 +699,8 @@ impl Engine { } // Push in reverse order - let chain_type = expr.into(); - self.eval_dot_index_chain_arguments( - global, caches, scope, this_ptr, rhs, *options, chain_type, idx_values, + global, caches, scope, this_ptr, expr, idx_values, rhs, )?; if !_arg_values.is_empty() { @@ -736,11 +731,11 @@ impl Engine { caches: &mut Caches, target: &mut Dynamic, idx: &mut Dynamic, + pos: Position, ) -> RhaiResultOf { let args = &mut [target, idx]; let hash = global.hash_idx_get(); let fn_name = crate::engine::FN_IDX_GET; - let pos = Position::NONE; let orig_level = global.level; global.level += 1; @@ -760,11 +755,11 @@ impl Engine { idx: &mut Dynamic, new_val: &mut Dynamic, is_ref_mut: bool, + pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { let hash = global.hash_idx_set(); let args = &mut [target, idx, new_val]; let fn_name = crate::engine::FN_IDX_SET; - let pos = Position::NONE; let orig_level = global.level; global.level += 1; @@ -774,7 +769,6 @@ impl Engine { } /// Get the value at the indexed position of a base type. - /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] may be [`NONE`][Position::NONE] and should be set afterwards. fn get_indexed_mut<'t>( &self, global: &mut GlobalRuntimeState, @@ -782,6 +776,7 @@ impl Engine { target: &'t mut Dynamic, idx: &mut Dynamic, idx_pos: Position, + op_pos: Position, _add_if_not_found: bool, use_indexers: bool, ) -> RhaiResultOf> { @@ -987,7 +982,7 @@ impl Engine { } _ if use_indexers => self - .call_indexer_get(global, caches, target, idx) + .call_indexer_get(global, caches, target, idx, op_pos) .map(Into::into), _ => Err(ERR::ErrorIndexingType( @@ -996,7 +991,7 @@ impl Engine { self.map_type_name(target.type_name()), self.map_type_name(idx.type_name()) ), - Position::NONE, + op_pos, ) .into()), } diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index bce0b87b..fa47b269 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -3,7 +3,8 @@ use super::GlobalRuntimeState; use crate::types::dynamic::Union; -use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, ERR}; +use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR}; +use std::borrow::Borrow; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -70,6 +71,9 @@ impl Engine { } /// Raise an error if any data size exceeds limit. + /// + /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE] + /// and should be set afterwards. pub(crate) fn raise_err_if_over_data_size_limit( &self, (_arr, _map, s): (usize, usize, usize), @@ -111,15 +115,20 @@ impl Engine { /// Check whether the size of a [`Dynamic`] is within limits. #[inline] - pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> { + pub(crate) fn check_data_size>( + &self, + value: T, + pos: Position, + ) -> RhaiResultOf { // If no data size limits, just return if !self.has_data_size_limit() { - return Ok(()); + return Ok(value); } - let sizes = Self::calc_data_sizes(value, true); + let sizes = Self::calc_data_sizes(value.borrow(), true); self.raise_err_if_over_data_size_limit(sizes) + .map(|_| value) .map_err(|err| err.fill_position(pos)) } @@ -128,7 +137,7 @@ impl Engine { /// Not available under `unchecked`. #[inline(always)] pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> { - self.check_data_size(value, Position::NONE) + self.check_data_size(value, Position::NONE).map(|_| ()) } /// Check if the number of operations stay within limit. @@ -153,14 +162,4 @@ impl Engine { .and_then(|p| p(num_operations)) .map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into())) } - - /// Check a result to ensure that it is valid. - #[inline] - pub(crate) fn check_return_value(&self, result: RhaiResult, pos: Position) -> RhaiResult { - if let Ok(ref r) = result { - self.check_data_size(r, pos)?; - } - - result - } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 58e45d8a..5a2455bb 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -54,12 +54,12 @@ impl Engine { scope: &'s mut Scope, this_ptr: &'s mut Dynamic, expr: &Expr, - ) -> RhaiResultOf<(Target<'s>, Position)> { + ) -> RhaiResultOf> { match expr { Expr::Variable(_, Some(_), _) => { self.search_scope_only(global, caches, scope, this_ptr, expr) } - Expr::Variable(v, None, _var_pos) => match &**v { + Expr::Variable(v, None, ..) => match &**v { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { @@ -86,7 +86,7 @@ impl Engine { |mut target| { // Module variables are constant target.set_access_mode(AccessMode::ReadOnly); - Ok((target.into(), *_var_pos)) + Ok(target.into()) }, ); } @@ -101,7 +101,7 @@ impl Engine { let mut target: Target = value.clone().into(); // Module variables are constant target.set_access_mode(AccessMode::ReadOnly); - return Ok((target, *_var_pos)); + return Ok(target); } } @@ -136,23 +136,23 @@ impl Engine { scope: &'s mut Scope, this_ptr: &'s mut Dynamic, expr: &Expr, - ) -> RhaiResultOf<(Target<'s>, Position)> { + ) -> RhaiResultOf> { // Make sure that the pointer indirection is taken only when absolutely necessary. - let (index, var_pos) = match expr { + let index = match expr { // Check if the variable is `this` - Expr::Variable(v, None, pos) if v.0.is_none() && v.3 == KEYWORD_THIS => { + Expr::Variable(v, None, ..) if v.0.is_none() && v.3 == KEYWORD_THIS => { return if this_ptr.is_null() { - Err(ERR::ErrorUnboundThis(*pos).into()) + Err(ERR::ErrorUnboundThis(expr.position()).into()) } else { - Ok((this_ptr.into(), *pos)) + Ok(this_ptr.into()) }; } - _ if global.always_search_scope => (0, expr.start_position()), - Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos), + _ if global.always_search_scope => 0, + Expr::Variable(_, Some(i), ..) => i.get() as usize, // Scripted function with the same name #[cfg(not(feature = "no_function"))] - Expr::Variable(v, None, pos) + Expr::Variable(v, None, ..) if global .lib .iter() @@ -161,9 +161,9 @@ impl Engine { { let val: Dynamic = crate::FnPtr::new_unchecked(v.3.as_str(), Default::default()).into(); - return Ok((val.into(), *pos)); + return Ok(val.into()); } - Expr::Variable(v, None, pos) => (v.0.map_or(0, NonZeroUsize::get), *pos), + Expr::Variable(v, None, ..) => v.0.map_or(0, NonZeroUsize::get), _ => unreachable!("Expr::Variable expected but gets {:?}", expr), }; @@ -174,10 +174,10 @@ impl Engine { match resolve_var(var_name, index, context) { Ok(Some(mut result)) => { result.set_access_mode(AccessMode::ReadOnly); - return Ok((result.into(), var_pos)); + return Ok(result.into()); } Ok(None) => (), - Err(err) => return Err(err.fill_position(var_pos)), + Err(err) => return Err(err.fill_position(expr.position())), } } @@ -191,10 +191,12 @@ impl Engine { Some(index) => index, None => { return match self.global_modules.iter().find_map(|m| m.get_var(var_name)) { - Some(val) => Ok((val.into(), var_pos)), - None => { - Err(ERR::ErrorVariableNotFound(var_name.to_string(), var_pos).into()) - } + Some(val) => Ok(val.into()), + None => Err(ERR::ErrorVariableNotFound( + var_name.to_string(), + expr.position(), + ) + .into()), } } } @@ -202,17 +204,10 @@ impl Engine { let val = scope.get_mut_by_index(index); - Ok((val.into(), var_pos)) + Ok(val.into()) } /// Evaluate an expression. - // - // # Implementation Notes - // - // Do not use the `?` operator within the main body as it makes this function return early, - // possibly by-passing important cleanup tasks at the end. - // - // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_expr( &self, global: &mut GlobalRuntimeState, @@ -256,7 +251,7 @@ impl Engine { } } else { self.search_namespace(global, caches, scope, this_ptr, expr) - .map(|(val, ..)| val.take_or_clone()) + .map(Target::take_or_clone) }; } @@ -286,10 +281,8 @@ impl Engine { let target = &mut concat; let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); - let root = ("", Position::NONE); - let result = x - .iter() + x.iter() .try_for_each(|expr| { let item = self .eval_expr(global, caches, scope, this_ptr, expr)? @@ -297,11 +290,10 @@ impl Engine { op_info.pos = expr.start_position(); - self.eval_op_assignment(global, caches, &op_info, target, root, item) + self.eval_op_assignment(global, caches, &op_info, expr, target, item) }) - .map(|_| concat.take_or_clone()); - - self.check_return_value(result, expr.start_position()) + .map(|_| concat.take_or_clone()) + .and_then(|r| self.check_data_size(r, expr.start_position())) } #[cfg(not(feature = "no_index"))] @@ -414,9 +406,8 @@ impl Engine { })?; let mut context = EvalContext::new(self, global, caches, scope, this_ptr); - let result = (custom_def.func)(&mut context, &expressions, &custom.state); - - self.check_return_value(result, expr.start_position()) + (custom_def.func)(&mut context, &expressions, &custom.state) + .and_then(|r| self.check_data_size(r, expr.start_position())) } Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1daf85ae..b4944850 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -26,6 +26,9 @@ pub use target::{calc_index, calc_offset_len, Target}; #[cfg(feature = "unchecked")] mod unchecked { use crate::{eval::GlobalRuntimeState, Dynamic, Engine, Position, RhaiResult, RhaiResultOf}; + use std::borrow::Borrow; + #[cfg(feature = "no_std")] + use std::prelude::v1::*; impl Engine { /// Check if the number of operations stay within limit. @@ -40,18 +43,12 @@ mod unchecked { /// Check whether the size of a [`Dynamic`] is within limits. #[inline(always)] - pub(crate) const fn check_data_size(&self, _: &Dynamic, _: Position) -> RhaiResultOf<()> { - Ok(()) - } - - /// Check a result to ensure that it is valid. - #[inline(always)] - pub(crate) const fn check_return_value( + pub(crate) const fn check_data_size>( &self, - result: RhaiResult, + value: T, _: Position, - ) -> RhaiResult { - result + ) -> RhaiResultOf { + Ok(value) } } } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index ae5a7890..8465657b 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -8,22 +8,13 @@ use crate::ast::{ use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::types::dynamic::AccessMode; use crate::types::RestoreOnDrop; -use crate::{ - Dynamic, Engine, ImmutableString, Position, RhaiResult, RhaiResultOf, Scope, ERR, INT, -}; +use crate::{Dynamic, Engine, ImmutableString, RhaiResult, RhaiResultOf, Scope, ERR, INT}; use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; impl Engine { /// Evaluate a statements block. - // - // # Implementation Notes - // - // Do not use the `?` operator within the main body as it makes this function return early, - // possibly by-passing important cleanup tasks at the end. - // - // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt_block( &self, global: &mut GlobalRuntimeState, @@ -112,13 +103,15 @@ impl Engine { global: &mut GlobalRuntimeState, caches: &mut Caches, op_info: &OpAssignment, + root: &Expr, target: &mut Target, - root: (&str, Position), mut new_val: Dynamic, ) -> RhaiResultOf<()> { // Assignment to constant variable? if target.is_read_only() { - return Err(ERR::ErrorAssignmentToConstant(root.0.to_string(), root.1).into()); + let name = root.get_variable_name(false).unwrap_or_default(); + let pos = root.start_position(); + return Err(ERR::ErrorAssignmentToConstant(name.to_string(), pos).into()); } if op_info.is_op_assignment() { @@ -166,14 +159,13 @@ impl Engine { *args[0] = self .exec_native_fn_call( global, caches, op, token, *hash_op, args, true, *op_pos, - ) - .map_err(|err| err.fill_position(op_info.pos))? + )? .0; } Err(err) => return Err(err), } - self.check_data_size(args[0], root.1)?; + self.check_data_size(&*args[0], root.position())?; } else { // Normal assignment @@ -190,13 +182,6 @@ impl Engine { } /// Evaluate a statement. - // - // # Implementation Notes - // - // Do not use the `?` operator within the main body as it makes this function return early, - // possibly by-passing important cleanup tasks at the end. - // - // Errors that are not recoverable, such as system errors or safety errors, can use `?`. pub(crate) fn eval_stmt( &self, global: &mut GlobalRuntimeState, @@ -234,29 +219,29 @@ impl Engine { .eval_expr(global, caches, scope, this_ptr, rhs)? .flatten(); - let (mut lhs_ptr, pos) = - self.search_namespace(global, caches, scope, this_ptr, lhs)?; + let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let var_name = x.3.as_str(); #[cfg(not(feature = "no_closure"))] // Also handle case where target is a `Dynamic` shared value // (returned by a variable resolver, for example) - let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); + let is_temp_result = !target.is_ref() && !target.is_shared(); #[cfg(feature = "no_closure")] let is_temp_result = !lhs_ptr.is_ref(); // Cannot assign to temp result from expression if is_temp_result { - return Err(ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into()); + return Err(ERR::ErrorAssignmentToConstant( + var_name.to_string(), + lhs.position(), + ) + .into()); } - self.track_operation(global, pos)?; + self.track_operation(global, lhs.position())?; - let root = (var_name, pos); - let lhs_ptr = &mut lhs_ptr; - - self.eval_op_assignment(global, caches, op_info, lhs_ptr, root, rhs_val)?; + self.eval_op_assignment(global, caches, op_info, lhs, &mut target, rhs_val)?; return Ok(Dynamic::UNIT); } diff --git a/src/eval/target.rs b/src/eval/target.rs index 64bfe3b3..9b846ca5 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,6 +1,5 @@ //! Type to hold a mutable reference to the target of an evaluation. -use crate::types::dynamic::Variant; use crate::{Dynamic, Position, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -180,7 +179,7 @@ impl<'a> Target<'a> { Self::RefMut(r) => r.is_shared(), #[cfg(not(feature = "no_closure"))] Self::SharedValue { .. } => true, - Self::TempValue(r) => r.is_shared(), + Self::TempValue(value) => value.is_shared(), #[cfg(not(feature = "no_index"))] Self::Bit { .. } | Self::BitField { .. } @@ -188,29 +187,6 @@ impl<'a> Target<'a> { | Self::StringChar { .. } => false, } } - /// Is the [`Target`] a specific type? - #[allow(dead_code)] - #[inline] - #[must_use] - pub fn is(&self) -> bool { - #[allow(unused_imports)] - use std::any::TypeId; - - match self { - Self::RefMut(r) => r.is::(), - #[cfg(not(feature = "no_closure"))] - Self::SharedValue { source, .. } => source.is::(), - Self::TempValue(r) => r.is::(), - #[cfg(not(feature = "no_index"))] - Self::Bit { .. } => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::BitField { .. } => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::BlobByte { .. } => TypeId::of::() == TypeId::of::(), - #[cfg(not(feature = "no_index"))] - Self::StringChar { .. } => TypeId::of::() == TypeId::of::(), - } - } /// Get the value of the [`Target`] as a [`Dynamic`], cloning a referenced value if necessary. #[inline] #[must_use] @@ -219,7 +195,7 @@ impl<'a> Target<'a> { Self::RefMut(r) => r.clone(), // Referenced value is cloned #[cfg(not(feature = "no_closure"))] Self::SharedValue { value, .. } => value, // Original shared value is simply taken - Self::TempValue(v) => v, // Owned value is simply taken + Self::TempValue(value) => value, // Owned value is simply taken #[cfg(not(feature = "no_index"))] Self::Bit { value, .. } => value, // boolean is taken #[cfg(not(feature = "no_index"))] @@ -259,7 +235,7 @@ impl<'a> Target<'a> { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { source, .. } => source, - Self::TempValue(v) => v, + Self::TempValue(value) => value, #[cfg(not(feature = "no_index"))] Self::Bit { source, .. } => source, #[cfg(not(feature = "no_index"))] @@ -407,7 +383,7 @@ impl Deref for Target<'_> { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { source, .. } => source, - Self::TempValue(ref r) => r, + Self::TempValue(ref value) => value, #[cfg(not(feature = "no_index"))] Self::Bit { ref value, .. } | Self::BitField { ref value, .. } @@ -440,7 +416,7 @@ impl DerefMut for Target<'_> { Self::RefMut(r) => r, #[cfg(not(feature = "no_closure"))] Self::SharedValue { source, .. } => &mut *source, - Self::TempValue(ref mut r) => r, + Self::TempValue(ref mut value) => value, #[cfg(not(feature = "no_index"))] Self::Bit { ref mut value, .. } | Self::BitField { ref mut value, .. } diff --git a/src/func/call.rs b/src/func/call.rs index 0d47eb24..1b9054e8 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -394,6 +394,7 @@ impl Engine { } // Run external function + let is_method = func.is_method(); let src = source.as_ref().map(|s| s.as_str()); let context = (self, name, src, &*global, pos).into(); @@ -403,13 +404,15 @@ impl Engine { Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) } else { f.call(context, args) + .and_then(|r| self.check_data_size(r, pos)) + .map_err(|err| err.fill_position(pos)) } } else { func.get_native_fn().unwrap()(context, args) + .and_then(|r| self.check_data_size(r, pos)) + .map_err(|err| err.fill_position(pos)) }; - let is_method = func.is_method(); - #[cfg(feature = "debugging")] { use crate::eval::{DebuggerEvent, DebuggerStatus}; @@ -440,13 +443,12 @@ impl Engine { global.debugger.rewind_call_stack(orig_call_stack_len); } - // Check the return value (including data sizes) - let result = self.check_return_value(_result, pos)?; + let result = _result?; // Check the data size of any `&mut` object, which may be changed. #[cfg(not(feature = "unchecked"))] if is_ref_mut && !args.is_empty() { - self.check_data_size(args[0], pos)?; + self.check_data_size(&*args[0], pos)?; } // See if the function match print/debug (which requires special processing) @@ -1189,14 +1191,14 @@ impl Engine { .map(|(value, ..)| arg_values.push(value.flatten())) })?; - let (mut target, _pos) = + let mut target = self.search_namespace(global, caches, scope, this_ptr, first_expr)?; if target.is_read_only() { target = target.into_owned(); } - self.track_operation(global, _pos)?; + self.track_operation(global, first_expr.position())?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1270,10 +1272,9 @@ impl Engine { // Get target reference to first argument let first_arg = &args_expr[0]; - let (target, _pos) = - self.search_scope_only(global, caches, scope, this_ptr, first_arg)?; + let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?; - self.track_operation(global, _pos)?; + self.track_operation(global, first_arg.position())?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1383,19 +1384,18 @@ impl Engine { Some(f) if f.is_plugin_fn() => { let context = (self, fn_name, module.id(), &*global, pos).into(); let f = f.get_plugin_fn().expect("plugin function"); - let result = if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { + if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) } else { f.call(context, &mut args) - }; - self.check_return_value(result, pos) + .and_then(|r| self.check_data_size(r, pos)) + } } Some(f) if f.is_native() => { let func = f.get_native_fn().expect("native function"); let context = (self, fn_name, module.id(), &*global, pos).into(); - let result = func(context, &mut args); - self.check_return_value(result, pos) + func(context, &mut args).and_then(|r| self.check_data_size(r, pos)) } Some(f) => unreachable!("unknown function type: {:?}", f), diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 0f5af48f..bb5a0c2a 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -92,7 +92,7 @@ pub fn get_hasher() -> ahash::AHasher { /// /// # Zeros /// -/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. +/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`. /// /// # Note /// @@ -107,9 +107,8 @@ pub fn calc_var_hash<'a>( // We always skip the first module let iter = modules.into_iter(); - let len = iter.len(); + iter.len().hash(s); iter.skip(1).for_each(|m| m.hash(s)); - len.hash(s); var_name.hash(s); match s.finish() { @@ -128,7 +127,7 @@ pub fn calc_var_hash<'a>( /// /// # Zeros /// -/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. +/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`. /// /// # Note /// @@ -144,9 +143,8 @@ pub fn calc_fn_hash<'a>( // We always skip the first module let iter = namespace.into_iter(); - let len = iter.len(); + iter.len().hash(s); iter.skip(1).for_each(|m| m.hash(s)); - len.hash(s); fn_name.hash(s); num.hash(s); @@ -162,7 +160,7 @@ pub fn calc_fn_hash<'a>( /// /// # Zeros /// -/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. +/// If the hash happens to be zero, it is mapped to `ALT_ZERO_HASH`. #[inline] #[must_use] pub fn calc_fn_hash_full( @@ -172,11 +170,10 @@ pub fn calc_fn_hash_full( let s = &mut get_hasher(); base.hash(s); let iter = params.into_iter(); - let len = iter.len(); + iter.len().hash(s); iter.for_each(|t| { t.hash(s); }); - len.hash(s); match s.finish() { 0 => ALT_ZERO_HASH, diff --git a/src/func/native.rs b/src/func/native.rs index ddd1d3e8..5010a2f4 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -303,7 +303,7 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self._call_fn_raw(fn_name, false, false, false, &mut args)?; + let result = self._call_fn_raw(fn_name, &mut args, false, false, false)?; let typ = self.engine().map_type_name(result.type_name()); @@ -328,7 +328,7 @@ impl<'a> NativeCallContext<'a> { let mut args: StaticVec<_> = arg_values.iter_mut().collect(); - let result = self._call_fn_raw(fn_name, true, false, false, &mut args)?; + let result = self._call_fn_raw(fn_name, &mut args, true, false, false)?; let typ = self.engine().map_type_name(result.type_name()); @@ -369,7 +369,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_function"))] let native_only = native_only && !crate::parser::is_anonymous_fn(name); - self._call_fn_raw(fn_name, native_only, is_ref_mut, is_method_call, args) + self._call_fn_raw(fn_name, args, native_only, is_ref_mut, is_method_call) } /// Call a registered native Rust function inside the call context. /// @@ -398,17 +398,17 @@ impl<'a> NativeCallContext<'a> { is_ref_mut: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { - self._call_fn_raw(fn_name, true, is_ref_mut, false, args) + self._call_fn_raw(fn_name, args, true, is_ref_mut, false) } /// Call a function (native Rust or scripted) inside the call context. fn _call_fn_raw( &self, fn_name: impl AsRef, + args: &mut [&mut Dynamic], native_only: bool, is_ref_mut: bool, is_method_call: bool, - args: &mut [&mut Dynamic], ) -> RhaiResult { let mut global = &mut self.global.clone(); let caches = &mut Caches::new(); diff --git a/src/types/error.rs b/src/types/error.rs index 1a8035e5..411e4ed1 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -198,7 +198,7 @@ impl fmt::Display for EvalAltResult { if s.starts_with(crate::engine::FN_SET) => { let prop = &s[crate::engine::FN_SET.len()..]; - write!(f, "Cannot modify property {prop} of constant")? + write!(f, "Cannot modify property '{prop}' of a constant")? } #[cfg(not(feature = "no_index"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_GET => { @@ -209,10 +209,10 @@ impl fmt::Display for EvalAltResult { } #[cfg(not(feature = "no_index"))] Self::ErrorNonPureMethodCallOnConstant(s, ..) if s == crate::engine::FN_IDX_SET => { - write!(f, "Cannot assign to indexer of constant")? + write!(f, "Cannot assign to the indexer of a constant")? } Self::ErrorNonPureMethodCallOnConstant(s, ..) => { - write!(f, "Non-pure method {s} cannot be called on constant")? + write!(f, "Non-pure method '{s}' cannot be called on a constant")? } Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant {s}")?, @@ -230,8 +230,8 @@ impl fmt::Display for EvalAltResult { Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?, Self::ErrorArithmetic(s, ..) => f.write_str(s)?, - Self::LoopBreak(true, ..) => f.write_str("'break' not inside a loop")?, - Self::LoopBreak(false, ..) => f.write_str("'continue' not inside a loop")?, + Self::LoopBreak(true, ..) => f.write_str("'break' must be inside a loop")?, + Self::LoopBreak(false, ..) => f.write_str("'continue' must be inside a loop")?, Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?,