Skip to content

Commit

Permalink
Merge pull request #154 from schungx/master
Browse files Browse the repository at this point in the history
Limiting script resources
  • Loading branch information
schungx committed May 18, 2020
2 parents 0513b68 + f4a528a commit e5584b2
Show file tree
Hide file tree
Showing 24 changed files with 1,278 additions and 345 deletions.
208 changes: 170 additions & 38 deletions README.md

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions RELEASES.md
@@ -0,0 +1,66 @@
Rhai Release Notes
==================

Version 0.14.1
==============

The major features for this release is modules, script resource limits, and speed improvements
(mainly due to avoiding allocations).

New features
------------

* Modules and _module resolvers_ allow loading external scripts under a module namespace.
A module can contain constant variables, Rust functions and Rhai functions.
* `export` variables and `private` functions.
* _Indexers_ for Rust types.
* Track script evaluation progress and terminate script run.
* Set limit on maximum number of operations allowed per script run.
* Set limit on maximum number of modules loaded per script run.
* A new API, `Engine::compile_scripts_with_scope`, can compile a list of script segments without needing to
first concatenate them together into one large string.
* Stepped `range` function with a custom step.

Speed improvements
------------------

### `StaticVec`

A script contains many lists - statements in a block, arguments to a function call etc.
In a typical script, most of these lists tend to be short - e.g. the vast majority of function calls contain
fewer than 4 arguments, while most statement blocks have fewer than 4-5 statements, with one or two being
the most common. Before, dynamic `Vec`'s are used to hold these short lists for very brief periods of time,
causing allocations churn.

In this version, large amounts of allocations are avoided by converting to a `StaticVec` -
a list type based on a static array for a small number of items (currently four) -
wherever possible plus other tricks. Most real-life scripts should see material speed increases.

### Pre-computed variable lookups

Almost all variable lookups, as well as lookups in loaded modules, are now pre-computed.
A variable's name is almost never used to search for the variable in the current scope.

_Getters_ and _setter_ function names are also pre-computed and cached, so no string allocations are
performed during a property get/set call.

### Pre-computed function call hashes

Lookup of all function calls, including Rust and Rhai ones, are now through pre-computed hashes.
The function name is no longer used to search for a function, making function call dispatches
much faster.

### Large Boxes for expressions and statements

The expression (`Expr`) and statement (`Stmt`) types are modified so that all of the variants contain only
one single `Box` to a large allocated structure containing _all_ the fields. This makes the `Expr` and
`Stmt` types very small (only one single pointer) and improves evaluation speed due to cache efficiency.

Error handling
--------------

Previously, when an error occurs inside a function call, the error position reported is the function
call site. This makes it difficult to diagnose the actual location of the error within the function.

A new error variant `EvalAltResult::ErrorInFunctionCall` is added in this version.
It wraps the internal error returned by the called function, including the error position within the function.
8 changes: 4 additions & 4 deletions src/any.rs
Expand Up @@ -19,15 +19,15 @@ use crate::stdlib::{
any::{type_name, Any, TypeId},
boxed::Box,
collections::HashMap,
fmt, mem, ptr,
fmt,
string::String,
vec::Vec,
};

#[cfg(not(feature = "no_std"))]
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.
/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`.
Expand Down Expand Up @@ -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`),
/// `bool`, `String`, `char`, `Vec<T>` (into `Array`) and `HashMap<String, T>` (into `Map`).
Expand Down Expand Up @@ -142,7 +142,7 @@ impl dyn Variant {
}
}

/// A dynamic type containing any value.
/// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union);

/// Internal `Dynamic` representation.
Expand Down
116 changes: 98 additions & 18 deletions src/api.rs
Expand Up @@ -21,7 +21,6 @@ use crate::engine::Map;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
collections::HashMap,
mem,
string::{String, ToString},
};
Expand Down Expand Up @@ -653,7 +652,10 @@ impl Engine {
let scripts = [script];
let stream = lex(&scripts);

parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level)
{
let mut peekable = stream.peekable();
parse_global_expr(&mut peekable, self, scope, self.optimization_level)
}
}

/// Evaluate a script file.
Expand Down Expand Up @@ -749,11 +751,10 @@ impl Engine {
scope: &mut Scope,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
// Since the AST will be thrown away afterwards, don't bother to optimize it
let ast = self.compile_with_scope_and_optimization_level(
scope,
&[script],
OptimizationLevel::None,
self.optimization_level,
)?;
self.eval_ast_with_scope(scope, &ast)
}
Expand Down Expand Up @@ -865,7 +866,7 @@ impl Engine {
scope: &mut Scope,
ast: &AST,
) -> Result<T, Box<EvalAltResult>> {
let result = self.eval_ast_with_scope_raw(scope, ast)?;
let (result, _) = self.eval_ast_with_scope_raw(scope, ast)?;

let return_type = self.map_type_name(result.type_name());

Expand All @@ -881,7 +882,7 @@ impl Engine {
&self,
scope: &mut Scope,
ast: &AST,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> Result<(Dynamic, u64), Box<EvalAltResult>> {
let mut state = State::new(ast.fn_lib());

ast.statements()
Expand All @@ -893,6 +894,7 @@ impl Engine {
EvalAltResult::Return(out, _) => Ok(out),
_ => Err(err),
})
.map(|v| (v, state.operations))
}

/// Evaluate a file, but throw away the result and only return error (if any).
Expand Down Expand Up @@ -1016,9 +1018,9 @@ impl Engine {
.ok_or_else(|| Box::new(EvalAltResult::ErrorFunctionNotFound(name.into(), pos)))?;

let state = State::new(fn_lib);
let args = args.as_mut();

let result =
self.call_script_fn(Some(scope), &state, name, fn_def, args.as_mut(), pos, 0)?;
let (result, _) = self.call_script_fn(Some(scope), state, name, fn_def, args, pos, 0)?;

let return_type = self.map_type_name(result.type_name());

Expand Down Expand Up @@ -1058,6 +1060,84 @@ impl Engine {
optimize_into_ast(self, scope, stmt, fn_lib, optimization_level)
}

/// Register a callback for script evaluation progress.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// use rhai::Engine;
///
/// let result = Arc::new(RwLock::new(0_u64));
/// let logger = result.clone();
///
/// let mut engine = Engine::new();
///
/// engine.on_progress(move |ops| {
/// if ops > 10000 {
/// false
/// } else if ops % 800 == 0 {
/// *logger.write().unwrap() = ops;
/// true
/// } else {
/// true
/// }
/// });
///
/// engine.consume("for x in range(0, 50000) {}")
/// .expect_err("should error");
///
/// assert_eq!(*result.read().unwrap(), 9600);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sync")]
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + Send + Sync + 'static) {
self.progress = Some(Box::new(callback));
}

/// Register a callback for script evaluation progress.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::cell::Cell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Rc::new(Cell::new(0_u64));
/// let logger = result.clone();
///
/// let mut engine = Engine::new();
///
/// engine.on_progress(move |ops| {
/// if ops > 10000 {
/// false
/// } else if ops % 800 == 0 {
/// logger.set(ops);
/// true
/// } else {
/// true
/// }
/// });
///
/// engine.consume("for x in range(0, 50000) {}")
/// .expect_err("should error");
///
/// assert_eq!(result.get(), 9600);
///
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "sync"))]
pub fn on_progress(&mut self, callback: impl Fn(u64) -> bool + 'static) {
self.progress = Some(Box::new(callback));
}

/// Override default action of `print` (print to stdout using `println!`)
///
/// # Example
Expand Down Expand Up @@ -1092,21 +1172,21 @@ impl Engine {
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Arc::new(RwLock::new(String::from("")));
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_print(move |s| logger.write().unwrap().push_str(s));
/// engine.on_print(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume("print(40 + 2);")?;
///
/// assert_eq!(*result.read().unwrap(), "42");
/// assert_eq!(*result.borrow(), "42");
/// # Ok(())
/// # }
/// ```
Expand Down Expand Up @@ -1149,21 +1229,21 @@ impl Engine {
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # use std::sync::RwLock;
/// # use std::sync::Arc;
/// # use std::cell::RefCell;
/// # use std::rc::Rc;
/// use rhai::Engine;
///
/// let result = Arc::new(RwLock::new(String::from("")));
/// let result = Rc::new(RefCell::new(String::from("")));
///
/// let mut engine = Engine::new();
///
/// // Override action of 'print' function
/// let logger = result.clone();
/// engine.on_debug(move |s| logger.write().unwrap().push_str(s));
/// engine.on_debug(move |s| logger.borrow_mut().push_str(s));
///
/// engine.consume(r#"debug("hello");"#)?;
///
/// assert_eq!(*result.read().unwrap(), r#""hello""#);
/// assert_eq!(*result.borrow(), r#""hello""#);
/// # Ok(())
/// # }
/// ```
Expand Down

0 comments on commit e5584b2

Please sign in to comment.