Limit function calls depth.

This commit is contained in:
Stephen Chung 2020-03-27 14:34:01 +08:00
parent cc8554d095
commit 337a96394f
4 changed files with 150 additions and 114 deletions

View File

@ -651,6 +651,18 @@ fn main() -> Result<(), EvalAltResult>
} }
``` ```
Engine configuration options
---------------------------
| Method | Description |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| `set_optimization_level` | Set the amount of script _optimizations_ performed. See [`script optimization`]. |
| `set_max_call_levels` | Set the maximum number of function call levels (default 50) to avoid infinite recursion. |
[`script optimization`]: #script-optimization
-------
Rhai Language Guide Rhai Language Guide
=================== ===================

View File

@ -726,11 +726,9 @@ impl<'e> Engine<'e> {
statements statements
}; };
let mut result = ().into_dynamic(); let result = statements.iter().try_fold(().into_dynamic(), |_, stmt| {
engine.eval_stmt(scope, stmt, 0)
for stmt in statements { })?;
result = engine.eval_stmt(scope, stmt)?;
}
if !retain_functions { if !retain_functions {
engine.clear_functions(); engine.clear_functions();
@ -829,7 +827,7 @@ impl<'e> Engine<'e> {
let result = statements let result = statements
.iter() .iter()
.try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt, 0))
.map(|_| ()); .map(|_| ());
if !retain_functions { if !retain_functions {
@ -889,7 +887,7 @@ impl<'e> Engine<'e> {
mut values: Vec<Dynamic>, mut values: Vec<Dynamic>,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
let mut values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); let mut values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect();
engine.call_fn_raw(name, &mut values, None, Position::none()) engine.call_fn_raw(name, &mut values, None, Position::none(), 0)
} }
call_fn_internal(self, name, args.into_vec()).and_then(|b| { call_fn_internal(self, name, args.into_vec()).and_then(|b| {

View File

@ -32,6 +32,7 @@ pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result<Dynamic, EvalAltRes
type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>; type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
pub(crate) const MAX_CALL_STACK_DEPTH: usize = 50;
pub(crate) const KEYWORD_PRINT: &str = "print"; pub(crate) const KEYWORD_PRINT: &str = "print";
pub(crate) const KEYWORD_DEBUG: &str = "debug"; pub(crate) const KEYWORD_DEBUG: &str = "debug";
pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast"; pub(crate) const KEYWORD_DUMP_AST: &str = "dump_ast";
@ -127,22 +128,26 @@ impl FunctionsLib {
/// # } /// # }
/// ``` /// ```
pub struct Engine<'e> { pub struct Engine<'e> {
/// Optimize the AST after compilation /// A hashmap containing all compiled functions known to the engine.
#[cfg(not(feature = "no_optimize"))]
pub(crate) optimization_level: OptimizationLevel,
/// A hashmap containing all compiled functions known to the engine
pub(crate) functions: HashMap<FnSpec<'e>, Box<FnAny>>, pub(crate) functions: HashMap<FnSpec<'e>, Box<FnAny>>,
/// A hashmap containing all script-defined functions /// A hashmap containing all script-defined functions.
pub(crate) fn_lib: FunctionsLib, pub(crate) fn_lib: FunctionsLib,
/// A hashmap containing all iterators known to the engine /// A hashmap containing all iterators known to the engine.
pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>, pub(crate) type_iterators: HashMap<TypeId, Box<IteratorFn>>,
/// A hashmap mapping type names to pretty-print names /// A hashmap mapping type names to pretty-print names.
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
/// Closure for implementing the print commands /// Closure for implementing the print commands.
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>, pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
/// Closure for implementing the debug commands /// Closure for implementing the debug commands.
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>, pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
/// Optimize the AST after compilation.
#[cfg(not(feature = "no_optimize"))]
pub(crate) optimization_level: OptimizationLevel,
/// Maximum levels of call-stack to prevent infinite recursion.
pub(crate) max_call_stack_depth: usize,
} }
impl Default for Engine<'_> { impl Default for Engine<'_> {
@ -174,6 +179,8 @@ impl Default for Engine<'_> {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[cfg(feature = "optimize_full")] #[cfg(feature = "optimize_full")]
optimization_level: OptimizationLevel::Full, optimization_level: OptimizationLevel::Full,
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
}; };
engine.register_core_lib(); engine.register_core_lib();
@ -197,6 +204,12 @@ impl Engine<'_> {
self.optimization_level = optimization_level self.optimization_level = optimization_level
} }
/// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows.
pub fn set_max_call_levels(&mut self, levels: usize) {
self.max_call_stack_depth = levels
}
/// Call a registered function /// Call a registered function
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
pub(crate) fn call_ext_fn_raw( pub(crate) fn call_ext_fn_raw(
@ -227,7 +240,12 @@ impl Engine<'_> {
args: &mut FnCallArgs, args: &mut FnCallArgs,
def_val: Option<&Dynamic>, def_val: Option<&Dynamic>,
pos: Position, pos: Position,
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
if level >= self.max_call_stack_depth {
return Err(EvalAltResult::ErrorStackOverflow(pos));
}
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) { if let Some(fn_def) = self.fn_lib.get_function(fn_name, args.len()) {
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -241,11 +259,11 @@ impl Engine<'_> {
.map(|(name, value)| (name, ScopeEntryType::Normal, value)), .map(|(name, value)| (name, ScopeEntryType::Normal, value)),
); );
// Evaluate // Evaluate the function at one higher level of call depth
// Convert return statement to return value
return self return self
.eval_stmt(&mut scope, &fn_def.body) .eval_stmt(&mut scope, &fn_def.body, level + 1)
.or_else(|err| match err { .or_else(|err| match err {
// Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
err => Err(err.set_position(pos)), err => Err(err.set_position(pos)),
}); });
@ -336,6 +354,7 @@ impl Engine<'_> {
src: Option<ScopeSource>, src: Option<ScopeSource>,
target: Option<&mut Variant>, target: Option<&mut Variant>,
dot_rhs: &Expr, dot_rhs: &Expr,
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
// Get the `this` reference. Either `src` or `target` should be `Some`. // Get the `this` reference. Either `src` or `target` should be `Some`.
fn get_this_ptr<'a>( fn get_this_ptr<'a>(
@ -360,7 +379,7 @@ impl Engine<'_> {
Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => {
let mut values = arg_expr_list let mut values = arg_expr_list
.iter() .iter()
.map(|arg_expr| self.eval_expr(scope, arg_expr)) .map(|arg_expr| self.eval_expr(scope, arg_expr, level))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let this_ptr = get_this_ptr(scope, src, target); let this_ptr = get_this_ptr(scope, src, target);
@ -369,14 +388,14 @@ impl Engine<'_> {
.chain(values.iter_mut().map(Dynamic::as_mut)) .chain(values.iter_mut().map(Dynamic::as_mut))
.collect(); .collect();
self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos) self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, 0)
} }
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target); let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
} }
// xxx.idx_lhs[idx_expr] // xxx.idx_lhs[idx_expr]
@ -388,13 +407,13 @@ impl Engine<'_> {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target); let this_ptr = get_this_ptr(scope, src, target);
( (
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
*pos, *pos,
) )
} }
// xxx.???[???][idx_expr] // xxx.???[???][idx_expr]
Expr::Index(_, _, _) => ( Expr::Index(_, _, _) => (
self.get_dot_val_helper(scope, src, target, idx_lhs)?, self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
*op_pos, *op_pos,
), ),
// Syntax error // Syntax error
@ -406,7 +425,7 @@ impl Engine<'_> {
} }
}; };
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.map(|(v, _)| v) .map(|(v, _)| v)
} }
@ -418,9 +437,9 @@ impl Engine<'_> {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target); let this_ptr = get_this_ptr(scope, src, target);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| { .and_then(|mut v| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
}) })
} }
// xxx.idx_lhs[idx_expr].rhs // xxx.idx_lhs[idx_expr].rhs
@ -432,13 +451,13 @@ impl Engine<'_> {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
let this_ptr = get_this_ptr(scope, src, target); let this_ptr = get_this_ptr(scope, src, target);
( (
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos)?, self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)?,
*pos, *pos,
) )
} }
// xxx.???[???][idx_expr].rhs // xxx.???[???][idx_expr].rhs
Expr::Index(_, _, _) => ( Expr::Index(_, _, _) => (
self.get_dot_val_helper(scope, src, target, idx_lhs)?, self.get_dot_val_helper(scope, src, target, idx_lhs, level)?,
*op_pos, *op_pos,
), ),
// Syntax error // Syntax error
@ -450,10 +469,10 @@ impl Engine<'_> {
} }
}; };
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos)
.and_then(|(mut v, _)| { .and_then(|(mut v, _)| {
self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs, level)
}) })
} }
// Syntax error // Syntax error
@ -477,6 +496,7 @@ impl Engine<'_> {
scope: &mut Scope, scope: &mut Scope,
dot_lhs: &Expr, dot_lhs: &Expr,
dot_rhs: &Expr, dot_rhs: &Expr,
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
@ -488,15 +508,16 @@ impl Engine<'_> {
// This is a variable property access (potential function call). // This is a variable property access (potential function call).
// Use a direct index into `scope` to directly mutate the variable value. // Use a direct index into `scope` to directly mutate the variable value.
self.get_dot_val_helper(scope, Some(entry), None, dot_rhs) self.get_dot_val_helper(scope, Some(entry), None, dot_rhs, level)
} }
// idx_lhs[idx_expr].??? // idx_lhs[idx_expr].???
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, op_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) = let (src_type, src, idx, mut target) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
let val = self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs); let this_ptr = target.as_mut();
let val = self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
if let Some(src) = src { if let Some(src) = src {
@ -524,8 +545,9 @@ impl Engine<'_> {
// {expr}.??? // {expr}.???
expr => { expr => {
let mut target = self.eval_expr(scope, expr)?; let mut target = self.eval_expr(scope, expr, level)?;
self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs) let this_ptr = target.as_mut();
self.get_dot_val_helper(scope, None, Some(this_ptr), dot_rhs, level)
} }
} }
} }
@ -549,8 +571,9 @@ impl Engine<'_> {
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
idx_expr: &Expr, idx_expr: &Expr,
level: usize,
) -> Result<INT, EvalAltResult> { ) -> Result<INT, EvalAltResult> {
self.eval_expr(scope, idx_expr)? self.eval_expr(scope, idx_expr, level)?
.downcast::<INT>() .downcast::<INT>()
.map(|v| *v) .map(|v| *v)
.map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position())) .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))
@ -612,8 +635,9 @@ impl Engine<'_> {
lhs: &'a Expr, lhs: &'a Expr,
idx_expr: &Expr, idx_expr: &Expr,
op_pos: Position, op_pos: Position,
level: usize,
) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> { ) -> Result<(IndexSourceType, Option<ScopeSource<'a>>, usize, Dynamic), EvalAltResult> {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr, level)?;
match lhs { match lhs {
// id[idx_expr] // id[idx_expr]
@ -638,7 +662,7 @@ impl Engine<'_> {
// (expr)[idx_expr] // (expr)[idx_expr]
expr => { expr => {
let val = self.eval_expr(scope, expr)?; let val = self.eval_expr(scope, expr, level)?;
self.get_indexed_value(&val, idx, idx_expr.position(), op_pos) self.get_indexed_value(&val, idx, idx_expr.position(), op_pos)
.map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v))
@ -727,18 +751,14 @@ impl Engine<'_> {
this_ptr: &mut Variant, this_ptr: &mut Variant,
dot_rhs: &Expr, dot_rhs: &Expr,
new_val: (&mut Dynamic, Position), new_val: (&mut Dynamic, Position),
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_rhs { match dot_rhs {
// xxx.id // xxx.id
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, new_val.0.as_mut()];
self.call_fn_raw( self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
&set_fn_name,
&mut [this_ptr, new_val.0.as_mut()],
None,
*pos,
)
} }
// xxx.lhs[idx_expr] // xxx.lhs[idx_expr]
@ -749,9 +769,9 @@ impl Engine<'_> {
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|v| { .and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr, level)?;
Self::update_indexed_value( Self::update_indexed_value(
v, v,
idx as usize, idx as usize,
@ -761,7 +781,8 @@ impl Engine<'_> {
}) })
.and_then(|mut v| { .and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
self.call_fn_raw(&set_fn_name, &mut [this_ptr, v.as_mut()], None, *pos) let mut args = [this_ptr, v.as_mut()];
self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
}) })
} }
@ -778,15 +799,15 @@ impl Engine<'_> {
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|mut v| { .and_then(|mut v| {
self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val) self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, level)
.map(|_| v) // Discard Ok return value .map(|_| v) // Discard Ok return value
}) })
.and_then(|mut v| { .and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
let mut args = [this_ptr, v.as_mut()];
self.call_fn_raw(&set_fn_name, &mut [this_ptr, v.as_mut()], None, *pos) self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
}) })
} }
@ -798,26 +819,23 @@ impl Engine<'_> {
Expr::Property(id, pos) => { Expr::Property(id, pos) => {
let get_fn_name = format!("{}{}", FUNC_GETTER, id); let get_fn_name = format!("{}{}", FUNC_GETTER, id);
self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos) self.call_fn_raw(&get_fn_name, &mut [this_ptr], None, *pos, 0)
.and_then(|v| { .and_then(|v| {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr, level)?;
let (mut target, _) = let (mut target, _) =
self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?; self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?;
let val_pos = new_val.1; let val_pos = new_val.1;
self.set_dot_val_helper(scope, target.as_mut(), rhs, new_val)?; let this_ptr = target.as_mut();
self.set_dot_val_helper(scope, this_ptr, rhs, new_val, level)?;
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
Self::update_indexed_value(v, idx as usize, target, val_pos) Self::update_indexed_value(v, idx as usize, target, val_pos)
}) })
.and_then(|mut v| { .and_then(|mut v| {
let set_fn_name = format!("{}{}", FUNC_SETTER, id); let set_fn_name = format!("{}{}", FUNC_SETTER, id);
self.call_fn_raw( let mut args = [this_ptr, v.as_mut()];
&set_fn_name, self.call_fn_raw(&set_fn_name, &mut args, None, *pos, 0)
&mut [this_ptr, v.as_mut()],
None,
*pos,
)
}) })
} }
@ -851,6 +869,7 @@ impl Engine<'_> {
dot_rhs: &Expr, dot_rhs: &Expr,
new_val: (&mut Dynamic, Position), new_val: (&mut Dynamic, Position),
op_pos: Position, op_pos: Position,
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match dot_lhs { match dot_lhs {
// id.??? // id.???
@ -865,8 +884,8 @@ impl Engine<'_> {
_ => { _ => {
// Avoid referencing scope which is used below as mut // Avoid referencing scope which is used below as mut
let entry = ScopeSource { name: id, ..entry }; let entry = ScopeSource { name: id, ..entry };
let this_ptr = target.as_mut();
let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
*scope.get_mut(entry) = target; *scope.get_mut(entry) = target;
@ -881,9 +900,10 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, op_pos) => { Expr::Index(lhs, idx_expr, op_pos) => {
let (src_type, src, idx, mut target) = let (src_type, src, idx, mut target) =
self.eval_index_expr(scope, lhs, idx_expr, *op_pos)?; self.eval_index_expr(scope, lhs, idx_expr, *op_pos, level)?;
let val_pos = new_val.1; let val_pos = new_val.1;
let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); let this_ptr = target.as_mut();
let val = self.set_dot_val_helper(scope, this_ptr, dot_rhs, new_val, level);
// In case the expression mutated `target`, we need to update it back into the scope because it is cloned. // In case the expression mutated `target`, we need to update it back into the scope because it is cloned.
if let Some(src) = src { if let Some(src) = src {
@ -918,7 +938,12 @@ impl Engine<'_> {
} }
/// Evaluate an expression /// Evaluate an expression
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> { fn eval_expr(
&mut self,
scope: &mut Scope,
expr: &Expr,
level: usize,
) -> Result<Dynamic, EvalAltResult> {
match expr { match expr {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Expr::FloatConstant(f, _) => Ok(f.into_dynamic()), Expr::FloatConstant(f, _) => Ok(f.into_dynamic()),
@ -932,15 +957,15 @@ impl Engine<'_> {
// lhs[idx_expr] // lhs[idx_expr]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(lhs, idx_expr, op_pos) => self Expr::Index(lhs, idx_expr, op_pos) => self
.eval_index_expr(scope, lhs, idx_expr, *op_pos) .eval_index_expr(scope, lhs, idx_expr, *op_pos, level)
.map(|(_, _, _, x)| x), .map(|(_, _, _, x)| x),
// Statement block // Statement block
Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt), Expr::Stmt(stmt, _) => self.eval_stmt(scope, stmt, level),
// lhs = rhs // lhs = rhs
Expr::Assignment(lhs, rhs, op_pos) => { Expr::Assignment(lhs, rhs, op_pos) => {
let mut rhs_val = self.eval_expr(scope, rhs)?; let mut rhs_val = self.eval_expr(scope, rhs, level)?;
match lhs.as_ref() { match lhs.as_ref() {
// name = rhs // name = rhs
@ -974,7 +999,7 @@ impl Engine<'_> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Index(idx_lhs, idx_expr, op_pos) => { Expr::Index(idx_lhs, idx_expr, op_pos) => {
let (src_type, src, idx, _) = let (src_type, src, idx, _) =
self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos, level)?;
if let Some(src) = src { if let Some(src) = src {
match src.typ { match src.typ {
@ -1006,6 +1031,7 @@ impl Engine<'_> {
dot_rhs, dot_rhs,
(&mut rhs_val, rhs.position()), (&mut rhs_val, rhs.position()),
*op_pos, *op_pos,
level,
), ),
// Error assignment to constant // Error assignment to constant
@ -1019,15 +1045,15 @@ impl Engine<'_> {
} }
} }
Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs), Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, lhs, rhs, level),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(contents, _) => { Expr::Array(contents, _) => {
let mut arr = Vec::new(); let mut arr = Vec::new();
for item in contents { contents.into_iter().try_for_each(|item| {
arr.push(self.eval_expr(scope, item)?); self.eval_expr(scope, item, level).map(|val| arr.push(val))
} })?;
Ok(Box::new(arr)) Ok(Box::new(arr))
} }
@ -1053,25 +1079,22 @@ impl Engine<'_> {
}; };
// Change the argument to a debug dump of the expressions // Change the argument to a debug dump of the expressions
let result = args_expr_list let mut result = args_expr_list
.iter() .iter()
.map(|expr| format!("{:#?}", expr)) .map(|expr| format!("{:#?}", expr))
.collect::<Vec<_>>(); .collect::<Vec<_>>()
.join("\n")
.into_dynamic();
// Redirect call to `print` // Redirect call to `print`
self.call_fn_raw( self.call_fn_raw(KEYWORD_PRINT, &mut [result.as_mut()], None, pos, level)
KEYWORD_PRINT,
&mut [result.join("\n").into_dynamic().as_mut()],
None,
pos,
)
} }
// type_of // type_of
KEYWORD_TYPE_OF KEYWORD_TYPE_OF
if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) => if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) =>
{ {
let r = self.eval_expr(scope, &args_expr_list[0])?; let r = self.eval_expr(scope, &args_expr_list[0], level)?;
Ok(self Ok(self
.map_type_name((*r).type_name()) .map_type_name((*r).type_name())
.to_string() .to_string()
@ -1083,7 +1106,7 @@ impl Engine<'_> {
if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) => if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) =>
{ {
let pos = args_expr_list[0].position(); let pos = args_expr_list[0].position();
let r = self.eval_expr(scope, &args_expr_list[0])?; let r = self.eval_expr(scope, &args_expr_list[0], level)?;
let script = let script =
r.downcast_ref::<String>() r.downcast_ref::<String>()
@ -1118,27 +1141,27 @@ impl Engine<'_> {
_ => { _ => {
let mut values = args_expr_list let mut values = args_expr_list
.iter() .iter()
.map(|expr| self.eval_expr(scope, expr)) .map(|expr| self.eval_expr(scope, expr, level))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let mut arg_values: Vec<_> = let mut arg_values: Vec<_> =
values.iter_mut().map(Dynamic::as_mut).collect(); values.iter_mut().map(Dynamic::as_mut).collect();
self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos) self.call_fn_raw(fn_name, &mut arg_values, def_val.as_ref(), *pos, level)
} }
} }
} }
Expr::And(lhs, rhs) => Ok(Box::new( Expr::And(lhs, rhs) => Ok(Box::new(
*self *self
.eval_expr(scope, &*lhs)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
})? })?
&& // Short-circuit using && && // Short-circuit using &&
*self *self
.eval_expr(scope, &*rhs)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
@ -1147,14 +1170,14 @@ impl Engine<'_> {
Expr::Or(lhs, rhs) => Ok(Box::new( Expr::Or(lhs, rhs) => Ok(Box::new(
*self *self
.eval_expr(scope, &*lhs)? .eval_expr(scope, &*lhs, level)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
})? })?
|| // Short-circuit using || || // Short-circuit using ||
*self *self
.eval_expr(scope, &*rhs)? .eval_expr(scope, &*rhs, level)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| { .map_err(|_| {
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
@ -1172,6 +1195,7 @@ impl Engine<'_> {
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
stmt: &Stmt, stmt: &Stmt,
level: usize,
) -> Result<Dynamic, EvalAltResult> { ) -> Result<Dynamic, EvalAltResult> {
match stmt { match stmt {
// No-op // No-op
@ -1179,7 +1203,7 @@ impl Engine<'_> {
// Expression as statement // Expression as statement
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
let result = self.eval_expr(scope, expr)?; let result = self.eval_expr(scope, expr, level)?;
Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) { Ok(if !matches!(expr.as_ref(), Expr::Assignment(_, _, _)) {
result result
@ -1192,15 +1216,10 @@ impl Engine<'_> {
// Block scope // Block scope
Stmt::Block(block, _) => { Stmt::Block(block, _) => {
let prev_len = scope.len(); let prev_len = scope.len();
let mut result: Result<Dynamic, EvalAltResult> = Ok(().into_dynamic());
for stmt in block.iter() { let result = block.iter().try_fold(().into_dynamic(), |_, stmt| {
result = self.eval_stmt(scope, stmt); self.eval_stmt(scope, stmt, level)
});
if result.is_err() {
break;
}
}
scope.rewind(prev_len); scope.rewind(prev_len);
@ -1209,14 +1228,14 @@ impl Engine<'_> {
// If-else statement // If-else statement
Stmt::IfThenElse(guard, if_body, else_body) => self Stmt::IfThenElse(guard, if_body, else_body) => self
.eval_expr(scope, guard)? .eval_expr(scope, guard, level)?
.downcast::<bool>() .downcast::<bool>()
.map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position()))
.and_then(|guard_val| { .and_then(|guard_val| {
if *guard_val { if *guard_val {
self.eval_stmt(scope, if_body) self.eval_stmt(scope, if_body, level)
} else if let Some(stmt) = else_body { } else if let Some(stmt) = else_body {
self.eval_stmt(scope, stmt.as_ref()) self.eval_stmt(scope, stmt.as_ref(), level)
} else { } else {
Ok(().into_dynamic()) Ok(().into_dynamic())
} }
@ -1224,10 +1243,10 @@ impl Engine<'_> {
// While loop // While loop
Stmt::While(guard, body) => loop { Stmt::While(guard, body) => loop {
match self.eval_expr(scope, guard)?.downcast::<bool>() { match self.eval_expr(scope, guard, level)?.downcast::<bool>() {
Ok(guard_val) => { Ok(guard_val) => {
if *guard_val { if *guard_val {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => { Err(EvalAltResult::ErrorLoopBreak(_)) => {
return Ok(().into_dynamic()) return Ok(().into_dynamic())
@ -1244,7 +1263,7 @@ impl Engine<'_> {
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
@ -1253,7 +1272,7 @@ impl Engine<'_> {
// For loop // For loop
Stmt::For(name, expr, body) => { Stmt::For(name, expr, body) => {
let arr = self.eval_expr(scope, expr)?; let arr = self.eval_expr(scope, expr, level)?;
let tid = Any::type_id(&*arr); let tid = Any::type_id(&*arr);
if let Some(iter_fn) = self.type_iterators.get(&tid) { if let Some(iter_fn) = self.type_iterators.get(&tid) {
@ -1268,7 +1287,7 @@ impl Engine<'_> {
for a in iter_fn(&arr) { for a in iter_fn(&arr) {
*scope.get_mut(entry) = a; *scope.get_mut(entry) = a;
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => break, Err(EvalAltResult::ErrorLoopBreak(_)) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
@ -1291,9 +1310,10 @@ impl Engine<'_> {
} }
// Return value // Return value
Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => { Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(EvalAltResult::Return(
Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) self.eval_expr(scope, a, level)?,
} *pos,
)),
// Empty throw // Empty throw
Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => {
@ -1302,7 +1322,7 @@ impl Engine<'_> {
// Throw value // Throw value
Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => {
let val = self.eval_expr(scope, a)?; let val = self.eval_expr(scope, a, level)?;
Err(EvalAltResult::ErrorRuntime( Err(EvalAltResult::ErrorRuntime(
val.downcast::<String>() val.downcast::<String>()
.map(|s| *s) .map(|s| *s)
@ -1313,7 +1333,7 @@ impl Engine<'_> {
// Let statement // Let statement
Stmt::Let(name, Some(expr), _) => { Stmt::Let(name, Some(expr), _) => {
let val = self.eval_expr(scope, expr)?; let val = self.eval_expr(scope, expr, level)?;
scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false);
Ok(().into_dynamic()) Ok(().into_dynamic())
} }
@ -1325,7 +1345,7 @@ impl Engine<'_> {
// Const statement // Const statement
Stmt::Const(name, expr, _) if expr.is_constant() => { Stmt::Const(name, expr, _) if expr.is_constant() => {
let val = self.eval_expr(scope, expr)?; let val = self.eval_expr(scope, expr, level)?;
scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true);
Ok(().into_dynamic()) Ok(().into_dynamic())
} }

View File

@ -60,6 +60,8 @@ pub enum EvalAltResult {
ErrorDotExpr(String, Position), ErrorDotExpr(String, Position),
/// Arithmetic error encountered. Wrapped value is the error message. /// Arithmetic error encountered. Wrapped value is the error message.
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
/// Call stack over maximum limit.
ErrorStackOverflow(Position),
/// Run-time error encountered. Wrapped value is the error message. /// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position), ErrorRuntime(String, Position),
/// Breaking out of loops - not an error if within a loop. /// Breaking out of loops - not an error if within a loop.
@ -105,6 +107,7 @@ impl EvalAltResult {
Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file",
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorRuntime(_, _) => "Runtime error",
Self::ErrorLoopBreak(_) => "Break statement not inside a loop", Self::ErrorLoopBreak(_) => "Break statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value", Self::Return(_, _) => "[Not Error] Function returns value",
@ -131,6 +134,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos),
Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorDotExpr(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos), Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos),
Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorRuntime(s, pos) => { Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
} }
@ -230,6 +234,7 @@ impl EvalAltResult {
| Self::ErrorMismatchOutputType(_, pos) | Self::ErrorMismatchOutputType(_, pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos) | Self::ErrorLoopBreak(pos)
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
@ -260,6 +265,7 @@ impl EvalAltResult {
| Self::ErrorMismatchOutputType(_, ref mut pos) | Self::ErrorMismatchOutputType(_, ref mut pos)
| Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorStackOverflow(ref mut pos)
| Self::ErrorRuntime(_, ref mut pos) | Self::ErrorRuntime(_, ref mut pos)
| Self::ErrorLoopBreak(ref mut pos) | Self::ErrorLoopBreak(ref mut pos)
| Self::Return(_, ref mut pos) => *pos = new_position, | Self::Return(_, ref mut pos) => *pos = new_position,