Limit modules loading.

This commit is contained in:
Stephen Chung 2020-05-15 21:40:54 +08:00
parent 55c97eb649
commit be97047e51
15 changed files with 147 additions and 69 deletions

View File

@ -23,8 +23,8 @@ Rhai's current features set:
* Relatively little `unsafe` code (yes there are some for performance reasons) * Relatively little `unsafe` code (yes there are some for performance reasons)
* Sand-boxed (the scripting [`Engine`] can be declared immutable which cannot mutate the containing environment * Sand-boxed (the scripting [`Engine`] can be declared immutable which cannot mutate the containing environment
unless explicitly allowed via `RefCell` etc.) unless explicitly allowed via `RefCell` etc.)
* Rugged (protection against [stack-overflow](#maximum-stack-depth) and [runaway scripts](#maximum-number-of-operations)) * Rugged (protection against [stack-overflow](#maximum-stack-depth) and [runaway scripts](#maximum-number-of-operations) etc.)
* Able to set limits on script resource usage (e.g. see [tracking progress](#tracking-progress)) * Able to track script evaluation [progress](#tracking-progress) and manually terminate a script run
* [`no-std`](#optional-features) support * [`no-std`](#optional-features) support
* [Function overloading](#function-overloading) * [Function overloading](#function-overloading)
* [Operator overloading](#operator-overloading) * [Operator overloading](#operator-overloading)
@ -2268,6 +2268,7 @@ The most important resources to watch out for are:
floating-point representations, in order to crash the system. floating-point representations, in order to crash the system.
* **Files**: A malignant script may continuously `import` an external module within an infinite loop, * **Files**: A malignant script may continuously `import` an external module within an infinite loop,
thereby putting heavy load on the file-system (or even the network if the file is not local). thereby putting heavy load on the file-system (or even the network if the file is not local).
Even when modules are not created from files, they still typically consume a lot of resources to load.
* **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens, * **Data**: A malignant script may attempt to read from and/or write to data that it does not own. If this happens,
it is a severe security breach and may put the entire system at risk. it is a severe security breach and may put the entire system at risk.
@ -2319,6 +2320,19 @@ engine.on_progress(|count| { // 'count' is the number of operatio
The closure passed to `Engine::on_progress` will be called once every operation. The closure passed to `Engine::on_progress` will be called once every operation.
Return `false` to terminate the script immediately. Return `false` to terminate the script immediately.
### Maximum number of modules
Rhai by default does not limit how many [modules] are loaded via the `import` statement.
This can be changed via the `Engine::set_max_modules` method, with zero being unlimited (the default).
```rust
let mut engine = Engine::new();
engine.set_max_modules(5); // allow loading only up to 5 modules
engine.set_max_modules(0); // allow unlimited modules
```
### Maximum stack depth ### Maximum stack depth
Rhai by default limits function calls to a maximum depth of 256 levels (28 levels in debug build). Rhai by default limits function calls to a maximum depth of 256 levels (28 levels in debug build).
@ -2646,7 +2660,7 @@ let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run
Or override it from Rust: Or override it from Rust:
```rust ```rust
fn alt_eval(script: String) -> Result<(), EvalAltResult> { fn alt_eval(script: String) -> Result<(), Box<EvalAltResult>> {
Err(format!("eval is evil! I refuse to run {}", script).into()) Err(format!("eval is evil! I refuse to run {}", script).into())
} }

View File

@ -19,7 +19,7 @@ use crate::stdlib::{
any::{type_name, Any, TypeId}, any::{type_name, Any, TypeId},
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
fmt, mem, ptr, fmt,
string::String, string::String,
vec::Vec, vec::Vec,
}; };
@ -27,7 +27,7 @@ use crate::stdlib::{
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
/// A trait to represent any type. /// Trait to represent any type.
/// ///
/// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type.
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. /// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
@ -81,7 +81,7 @@ impl<T: Any + Clone> Variant for T {
} }
} }
/// A trait to represent any type. /// Trait to represent any type.
/// ///
/// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`), /// `From<_>` is implemented for `i64` (`i32` if `only_i32`), `f64` (if not `no_float`),
/// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`). /// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`).
@ -142,7 +142,7 @@ impl dyn Variant {
} }
} }
/// A dynamic type containing any value. /// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union); pub struct Dynamic(pub(crate) Union);
/// Internal `Dynamic` representation. /// Internal `Dynamic` representation.

View File

@ -21,7 +21,6 @@ use crate::engine::Map;
use crate::stdlib::{ use crate::stdlib::{
any::{type_name, TypeId}, any::{type_name, TypeId},
boxed::Box, boxed::Box,
collections::HashMap,
mem, mem,
string::{String, ToString}, string::{String, ToString},
}; };
@ -1016,11 +1015,10 @@ impl Engine {
.get_function_by_signature(name, args.len(), true) .get_function_by_signature(name, args.len(), true)
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?; .ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;
let mut state = State::new(fn_lib); let state = State::new(fn_lib);
let args = args.as_mut(); let args = args.as_mut();
let (result, _) = let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?;
self.call_script_fn(Some(scope), &mut state, name, fn_def, args, pos, 0, 0)?;
let return_type = self.map_type_name(result.type_name()); let return_type = self.map_type_name(result.type_name());

View File

@ -22,7 +22,6 @@ use crate::parser::ModuleRef;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Cow,
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
format, format,
@ -36,13 +35,13 @@ use crate::stdlib::{
vec::Vec, vec::Vec,
}; };
/// An dynamic array of `Dynamic` values. /// Variable-sized array of `Dynamic` values.
/// ///
/// Not available under the `no_index` feature. /// Not available under the `no_index` feature.
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>; pub type Array = Vec<Dynamic>;
/// An dynamic hash map of `Dynamic` values with `String` keys. /// Hash map of `Dynamic` values with `String` keys.
/// ///
/// Not available under the `no_object` feature. /// Not available under the `no_object` feature.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -154,16 +153,20 @@ pub struct State<'a> {
/// Number of operations performed. /// Number of operations performed.
pub operations: u64, pub operations: u64,
/// Number of modules loaded.
pub modules: u64,
} }
impl<'a> State<'a> { impl<'a> State<'a> {
/// Create a new `State`. /// Create a new `State`.
pub fn new(fn_lib: &'a FunctionsLib) -> Self { pub fn new(fn_lib: &'a FunctionsLib) -> Self {
Self { Self {
always_search: false,
fn_lib, fn_lib,
always_search: false,
scope_level: 0, scope_level: 0,
operations: 0, operations: 0,
modules: 0,
} }
} }
/// Does a certain script-defined function exist in the `State`? /// Does a certain script-defined function exist in the `State`?
@ -322,8 +325,10 @@ pub struct Engine {
/// ///
/// Defaults to 28 for debug builds and 256 for non-debug builds. /// Defaults to 28 for debug builds and 256 for non-debug builds.
pub(crate) max_call_stack_depth: usize, pub(crate) max_call_stack_depth: usize,
/// Maximum number of operations to run. /// Maximum number of operations allowed to run.
pub(crate) max_operations: Option<NonZeroU64>, pub(crate) max_operations: Option<NonZeroU64>,
/// Maximum number of modules allowed to load.
pub(crate) max_modules: Option<NonZeroU64>,
} }
impl Default for Engine { impl Default for Engine {
@ -363,6 +368,7 @@ impl Default for Engine {
max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_operations: None, max_operations: None,
max_modules: None,
}; };
#[cfg(feature = "no_stdlib")] #[cfg(feature = "no_stdlib")]
@ -503,6 +509,7 @@ impl Engine {
max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_operations: None, max_operations: None,
max_modules: None,
} }
} }
@ -545,6 +552,13 @@ impl Engine {
pub fn set_max_operations(&mut self, operations: u64) { pub fn set_max_operations(&mut self, operations: u64) {
self.max_operations = NonZeroU64::new(operations); self.max_operations = NonZeroU64::new(operations);
} }
/// Set the maximum number of imported modules allowed for a script (0 for unlimited).
#[cfg(not(feature = "unchecked"))]
pub fn set_max_modules(&mut self, modules: u64) {
self.max_modules = NonZeroU64::new(modules);
}
/// Set the module resolution service used by the `Engine`. /// Set the module resolution service used by the `Engine`.
/// ///
/// Not available under the `no_module` feature. /// Not available under the `no_module` feature.
@ -582,11 +596,9 @@ impl Engine {
// First search in script-defined functions (can override built-in) // First search in script-defined functions (can override built-in)
if hashes.1 > 0 { if hashes.1 > 0 {
if let Some(fn_def) = state.get_function(hashes.1) { if let Some(fn_def) = state.get_function(hashes.1) {
let ops = state.operations; let (result, state2) =
let (result, operations) = self.call_script_fn(scope, *state, fn_name, fn_def, args, pos, level)?;
self.call_script_fn(scope, &state, fn_name, fn_def, args, pos, level, ops)?; *state = state2;
state.operations = operations;
self.inc_operations(state, pos)?;
return Ok((result, false)); return Ok((result, false));
} }
} }
@ -701,24 +713,23 @@ impl Engine {
/// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`! /// **DO NOT** reuse the argument values unless for the first `&mut` argument - all others are silently replaced by `()`!
pub(crate) fn call_script_fn<'s>( pub(crate) fn call_script_fn<'s>(
&self, &self,
scope: Option<&mut Scope<'s>>, scope: Option<&mut Scope>,
state: &State, mut state: State<'s>,
fn_name: &str, fn_name: &str,
fn_def: &FnDef, fn_def: &FnDef,
args: &mut FnCallArgs, args: &mut FnCallArgs,
pos: Position, pos: Position,
level: usize, level: usize,
operations: u64, ) -> Result<(Dynamic, State<'s>), Box<EvalAltResult>> {
) -> Result<(Dynamic, u64), Box<EvalAltResult>> { self.inc_operations(&mut state, pos)?;
let orig_scope_level = state.scope_level;
state.scope_level += 1;
match scope { match scope {
// Extern scope passed in which is not empty // Extern scope passed in which is not empty
Some(scope) if scope.len() > 0 => { Some(scope) if scope.len() > 0 => {
let scope_len = scope.len(); let scope_len = scope.len();
let mut local_state = State::new(state.fn_lib);
local_state.operations = operations;
self.inc_operations(&mut local_state, pos)?;
local_state.scope_level += 1;
// Put arguments into scope as variables // Put arguments into scope as variables
scope.extend( scope.extend(
@ -730,14 +741,14 @@ impl Engine {
args.into_iter().map(|v| mem::take(*v)), args.into_iter().map(|v| mem::take(*v)),
) )
.map(|(name, value)| { .map(|(name, value)| {
let var_name = unsafe_cast_var_name(name.as_str(), &local_state); let var_name = unsafe_cast_var_name(name.as_str(), &mut state);
(var_name, ScopeEntryType::Normal, value) (var_name, ScopeEntryType::Normal, value)
}), }),
); );
// Evaluate the function at one higher level of call depth // Evaluate the function at one higher level of call depth
let result = self let result = self
.eval_stmt(scope, &mut local_state, &fn_def.body, level + 1) .eval_stmt(scope, &mut state, &fn_def.body, level + 1)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -756,19 +767,14 @@ impl Engine {
}); });
// Remove all local variables // Remove all local variables
// No need to reset `state.scope_level` because it is thrown away
scope.rewind(scope_len); scope.rewind(scope_len);
state.scope_level = orig_scope_level;
return result.map(|v| (v, local_state.operations)); return result.map(|v| (v, state));
} }
// No new scope - create internal scope // No new scope - create internal scope
_ => { _ => {
let mut scope = Scope::new(); let mut scope = Scope::new();
let mut local_state = State::new(state.fn_lib);
local_state.operations = operations;
self.inc_operations(&mut local_state, pos)?;
local_state.scope_level += 1;
// Put arguments into scope as variables // Put arguments into scope as variables
scope.extend( scope.extend(
@ -783,9 +789,8 @@ impl Engine {
); );
// Evaluate the function at one higher level of call depth // Evaluate the function at one higher level of call depth
// No need to reset `state.scope_level` because it is thrown away let result = self
return self .eval_stmt(&mut scope, &mut state, &fn_def.body, level + 1)
.eval_stmt(&mut scope, &mut local_state, &fn_def.body, level + 1)
.or_else(|err| match *err { .or_else(|err| match *err {
// Convert return statement to return value // Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x), EvalAltResult::Return(x, _) => Ok(x),
@ -801,8 +806,10 @@ impl Engine {
err, err,
pos, pos,
))), ))),
}) });
.map(|v| (v, local_state.operations));
state.scope_level = orig_scope_level;
return result.map(|v| (v, state));
} }
} }
} }
@ -1512,11 +1519,9 @@ impl Engine {
// 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) = module.get_qualified_scripted_fn(*hash_fn_def) { if let Some(fn_def) = module.get_qualified_scripted_fn(*hash_fn_def) {
let args = args.as_mut(); let args = args.as_mut();
let ops = state.operations; let (result, state2) =
let (result, operations) = self.call_script_fn(None, *state, name, fn_def, args, *pos, level)?;
self.call_script_fn(None, state, name, fn_def, args, *pos, level, ops)?; *state = state2;
state.operations = operations;
self.inc_operations(state, *pos)?;
Ok(result) Ok(result)
} else { } else {
// Then search in Rust functions // Then search in Rust functions
@ -1792,13 +1797,20 @@ impl Engine {
// Import statement // Import statement
Stmt::Import(x) => { Stmt::Import(x) => {
let (expr, (name, pos)) = x.as_ref();
#[cfg(feature = "no_module")] #[cfg(feature = "no_module")]
unreachable!(); unreachable!();
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
let (expr, (name, pos)) = x.as_ref();
// Guard against too many modules
if let Some(max) = self.max_modules {
if state.modules >= max.get() {
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
}
}
if let Some(path) = self if let Some(path) = self
.eval_expr(scope, state, &expr, level)? .eval_expr(scope, state, &expr, level)?
.try_cast::<String>() .try_cast::<String>()
@ -1810,7 +1822,10 @@ impl Engine {
let mod_name = unsafe_cast_var_name(name, &state); let mod_name = unsafe_cast_var_name(name, &state);
scope.push_module(mod_name, module); scope.push_module(mod_name, module);
state.modules += 1;
self.inc_operations(state, *pos)?; self.inc_operations(state, *pos)?;
Ok(Default::default()) Ok(Default::default())
} else { } else {
Err(Box::new(EvalAltResult::ErrorModuleNotFound( Err(Box::new(EvalAltResult::ErrorModuleNotFound(

View File

@ -11,7 +11,7 @@ use crate::scope::Scope;
use crate::stdlib::{boxed::Box, string::ToString}; use crate::stdlib::{boxed::Box, string::ToString};
/// A trait to create a Rust anonymous function from a script. /// Trait to create a Rust anonymous function from a script.
pub trait Func<ARGS, RET> { pub trait Func<ARGS, RET> {
type Output; type Output;

View File

@ -82,7 +82,7 @@ pub enum NativeFunctionABI {
Method, Method,
} }
/// A trait implemented by all native Rust functions that are callable by Rhai. /// Trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub trait NativeCallable { pub trait NativeCallable {
/// Get the ABI type of a native Rust function. /// Get the ABI type of a native Rust function.
@ -91,7 +91,7 @@ pub trait NativeCallable {
fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>; fn call(&self, args: &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
} }
/// A trait implemented by all native Rust functions that are callable by Rhai. /// Trait implemented by all native Rust functions that are callable by Rhai.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait NativeCallable: Send + Sync { pub trait NativeCallable: Send + Sync {
/// Get the ABI type of a native Rust function. /// Get the ABI type of a native Rust function.

View File

@ -10,7 +10,7 @@ use crate::result::EvalAltResult;
use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString};
/// A trait to register custom functions with the `Engine`. /// Trait to register custom functions with the `Engine`.
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {
/// Register a custom function with the `Engine`. /// Register a custom function with the `Engine`.
/// ///
@ -42,7 +42,7 @@ pub trait RegisterFn<FN, ARGS, RET> {
fn register_fn(&mut self, name: &str, f: FN); fn register_fn(&mut self, name: &str, f: FN);
} }
/// A trait to register custom functions that return `Dynamic` values with the `Engine`. /// Trait to register custom functions that return `Dynamic` values with the `Engine`.
pub trait RegisterDynamicFn<FN, ARGS> { pub trait RegisterDynamicFn<FN, ARGS> {
/// Register a custom function returning `Dynamic` values with the `Engine`. /// Register a custom function returning `Dynamic` values with the `Engine`.
/// ///
@ -69,7 +69,7 @@ pub trait RegisterDynamicFn<FN, ARGS> {
fn register_dynamic_fn(&mut self, name: &str, f: FN); fn register_dynamic_fn(&mut self, name: &str, f: FN);
} }
/// A trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`. /// Trait to register fallible custom functions returning `Result<_, Box<EvalAltResult>>` with the `Engine`.
pub trait RegisterResultFn<FN, ARGS, RET> { pub trait RegisterResultFn<FN, ARGS, RET> {
/// Register a custom fallible function with the `Engine`. /// Register a custom fallible function with the `Engine`.
/// ///

View File

@ -116,6 +116,7 @@ pub use parser::FLOAT;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub use module::ModuleResolver; pub use module::ModuleResolver;
/// Module containing all built-in _module resolvers_ available to Rhai.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mod module_resolvers { pub mod module_resolvers {
pub use crate::module::resolvers::*; pub use crate::module::resolvers::*;

View File

@ -769,7 +769,7 @@ impl ModuleRef {
} }
} }
/// A trait that encapsulates a module resolution service. /// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub trait ModuleResolver { pub trait ModuleResolver {
@ -783,7 +783,7 @@ pub trait ModuleResolver {
) -> Result<Module, Box<EvalAltResult>>; ) -> Result<Module, Box<EvalAltResult>>;
} }
/// A trait that encapsulates a module resolution service. /// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait ModuleResolver: Send + Sync { pub trait ModuleResolver: Send + Sync {
@ -812,7 +812,7 @@ mod file {
use super::*; use super::*;
use crate::stdlib::path::PathBuf; use crate::stdlib::path::PathBuf;
/// A module resolution service that loads module script files from the file system. /// Module resolution service that loads module script files from the file system.
/// ///
/// The `new_with_path` and `new_with_path_and_extension` constructor functions /// The `new_with_path` and `new_with_path_and_extension` constructor functions
/// allow specification of a base directory with module path used as a relative path offset /// allow specification of a base directory with module path used as a relative path offset
@ -949,7 +949,7 @@ mod file {
mod stat { mod stat {
use super::*; use super::*;
/// A module resolution service that serves modules added into it. /// Module resolution service that serves modules added into it.
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -13,7 +13,6 @@ use crate::token::Position;
use crate::stdlib::{ use crate::stdlib::{
boxed::Box, boxed::Box,
collections::HashMap,
iter::empty, iter::empty,
string::{String, ToString}, string::{String, ToString},
vec, vec,

View File

@ -1,4 +1,4 @@
//! This module contains all built-in _packages_ available to Rhai, plus facilities to define custom packages. //! Module containing all built-in _packages_ available to Rhai, plus facilities to define custom packages.
use crate::fn_native::{NativeCallable, SharedIteratorFunction}; use crate::fn_native::{NativeCallable, SharedIteratorFunction};
use crate::module::Module; use crate::module::Module;
@ -89,7 +89,7 @@ impl PackagesCollection {
} }
} }
/// This macro makes it easy to define a _package_ (which is basically a shared module) /// Macro that makes it easy to define a _package_ (which is basically a shared module)
/// and register functions into it. /// and register functions into it.
/// ///
/// Functions can be added to the package using the standard module methods such as /// Functions can be added to the package using the standard module methods such as

View File

@ -81,6 +81,8 @@ pub enum EvalAltResult {
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
/// Number of operations over maximum limit. /// Number of operations over maximum limit.
ErrorTooManyOperations(Position), ErrorTooManyOperations(Position),
/// Modules over maximum limit.
ErrorTooManyModules(Position),
/// Call stack over maximum limit. /// Call stack over maximum limit.
ErrorStackOverflow(Position), ErrorStackOverflow(Position),
/// The script is prematurely terminated. /// The script is prematurely terminated.
@ -142,6 +144,7 @@ impl EvalAltResult {
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorTooManyOperations(_) => "Too many operations", Self::ErrorTooManyOperations(_) => "Too many operations",
Self::ErrorTooManyModules(_) => "Too many modules imported",
Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorTerminated(_) => "Script terminated.", Self::ErrorTerminated(_) => "Script terminated.",
Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorRuntime(_, _) => "Runtime error",
@ -190,6 +193,7 @@ impl fmt::Display for EvalAltResult {
| Self::ErrorInExpr(pos) | Self::ErrorInExpr(pos)
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
| Self::ErrorTooManyModules(pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorTerminated(pos) => write!(f, "{} ({})", desc, pos), | Self::ErrorTerminated(pos) => write!(f, "{} ({})", desc, pos),
@ -308,6 +312,7 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
| Self::ErrorTooManyModules(pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorTerminated(pos) | Self::ErrorTerminated(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
@ -346,6 +351,7 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorTooManyOperations(pos) | Self::ErrorTooManyOperations(pos)
| Self::ErrorTooManyModules(pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorTerminated(pos) | Self::ErrorTerminated(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)

View File

@ -36,7 +36,7 @@ pub struct Entry<'a> {
pub expr: Option<Box<Expr>>, pub expr: Option<Box<Expr>>,
} }
/// A type containing information about the current scope. /// Type containing information about the current scope.
/// Useful for keeping state between `Engine` evaluation runs. /// Useful for keeping state between `Engine` evaluation runs.
/// ///
/// # Example /// # Example

View File

@ -2,7 +2,6 @@
use crate::any::Variant; use crate::any::Variant;
use crate::engine::State; use crate::engine::State;
use crate::utils::StaticVec;
use crate::stdlib::{ use crate::stdlib::{
any::{Any, TypeId}, any::{Any, TypeId},
@ -10,7 +9,6 @@ use crate::stdlib::{
boxed::Box, boxed::Box,
mem, ptr, mem, ptr,
string::ToString, string::ToString,
vec::Vec,
}; };
/// Cast a type into another type. /// Cast a type into another type.

View File

@ -72,13 +72,60 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r#" r#"
import "hello" as h; import "hello" as h1;
h::answer import "hello" as h2;
h2::answer
"# "#
)?, )?,
42 42
); );
engine.set_max_modules(5);
assert!(matches!(
*engine
.eval::<()>(
r#"
for x in range(0, 10) {
import "hello" as h;
}
"#
)
.expect_err("should error"),
EvalAltResult::ErrorTooManyModules(_)
));
assert!(matches!(
*engine
.eval::<()>(
r#"
fn foo() {
import "hello" as h;
}
for x in range(0, 10) {
foo();
}
"#
)
.expect_err("should error"),
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
));
engine.set_max_modules(0);
engine.eval::<()>(
r#"
fn foo() {
import "hello" as h;
}
for x in range(0, 10) {
foo();
}
"#,
)?;
Ok(()) Ok(())
} }