Add Rhai book.

This commit is contained in:
Stephen Chung
2020-06-20 12:06:17 +08:00
parent 7e80d62df5
commit c7f1e12d6a
101 changed files with 3827 additions and 0 deletions

11
doc/src/safety/checked.md Normal file
View File

@@ -0,0 +1,11 @@
Checked Arithmetic
=================
{{#include ../links.md}}
By default, all arithmetic calculations in Rhai are _checked_, meaning that the script terminates
with an error whenever it detects a numeric over-flow/under-flow condition or an invalid
floating-point operation, instead of crashing the entire system.
This checking can be turned off via the [`unchecked`] feature for higher performance
(but higher risks as well).

View File

@@ -0,0 +1,40 @@
Maximum Size of Arrays
=====================
{{#include ../links.md}}
Limiting How Large Arrays Can Grow
---------------------------------
Rhai by default does not limit how large an [array] can be.
This can be changed via the `Engine::set_max_array_size` method, with zero being unlimited (the default).
A script attempting to create an array literal larger than the maximum will terminate with a parse error.
Any script operation that produces an array larger than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_array_size(500); // allow arrays only up to 500 items
engine.set_max_array_size(0); // allow unlimited arrays
```
Setting Maximum Size
-------------------
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
an array's size without Rhai noticing until the very end.
For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array;
if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size.
As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual
array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays]
and [object maps] contained within each array to make sure that the _aggregate_ sizes of none of these data structures
exceed their respective maximum size limits (if any).

View File

@@ -0,0 +1,31 @@
Maximum Call Stack Depth
=======================
{{#include ../links.md}}
Limiting How Stack Usage by Scripts
----------------------------------
Rhai by default limits function calls to a maximum depth of 128 levels (16 levels in debug build).
This limit may be changed via the `Engine::set_max_call_levels` method.
A script exceeding the maximum call stack depth will terminate with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_call_levels(10); // allow only up to 10 levels of function calls
engine.set_max_call_levels(0); // allow no function calls at all (max depth = zero)
```
Setting Maximum Stack Depth
--------------------------
When setting this limit, care must be also taken to the evaluation depth of each _statement_
within a function. It is entirely possible for a malicious script to embed a recursive call deep
inside a nested expression or statement block (see [maximum statement depth]).

View File

@@ -0,0 +1,40 @@
Maximum Size of Object Maps
==========================
{{#include ../links.md}}
Limiting How Large Object Maps Can Grow
--------------------------------------
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
This can be changed via the `Engine::set_max_map_size` method, with zero being unlimited (the default).
A script attempting to create an object map literal with more properties than the maximum will terminate with a parse error.
Any script operation that produces an object map with more properties than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_map_size(500); // allow object maps with only up to 500 properties
engine.set_max_map_size(0); // allow unlimited object maps
```
Setting Maximum Size
-------------------
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
an object map's size without Rhai noticing until the very end.
For instance, the built-in '`+`' operator for object maps concatenates two object maps together to form one larger object map;
if both object maps are _slightly_ below the maximum size limit, the resultant object map may be almost _twice_ the maximum size.
As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual
object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all [strings], [arrays]
and [object maps] contained within each object map to make sure that the _aggregate_ sizes of none of these data structures
exceed their respective maximum size limits (if any).

View File

@@ -0,0 +1,24 @@
Maximum Number of Modules
========================
{{#include ../links.md}}
Rhai by default does not limit how many [modules] can be loaded via [`import`] statements.
This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number
of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether.
A script attempting to load more than the maximum number of modules will terminate with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance
(but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_modules(5); // allow loading only up to 5 modules
engine.set_max_modules(0); // disallow loading any module (maximum = zero)
engine.set_max_modules(1000); // set to a large number for effectively unlimited modules
```

View File

@@ -0,0 +1,43 @@
Maximum Number of Operations
===========================
{{#include ../links.md}}
Limiting How Long a Script Can Run
---------------------------------
Rhai by default does not limit how much time or CPU a script consumes.
This can be changed via the `Engine::set_max_operations` method, with zero being unlimited (the default).
The _operations count_ is intended to be a very course-grained measurement of the amount of CPU that a script
has consumed, allowing the system to impose a hard upper limit on computing resources.
A script exceeding the maximum operations count terminates with an error result.
This can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_operations(500); // allow only up to 500 operations for this script
engine.set_max_operations(0); // allow unlimited operations
```
What Does One _Operation_ Mean
-----------------------------
The concept of one single _operation_ in Rhai is volatile - it roughly equals one expression node,
loading one variable/constant, one operator call, one iteration of a loop, or one function call etc.
with sub-expressions, statements and function calls executed inside these contexts accumulated on top.
A good rule-of-thumb is that one simple non-trivial expression consumes on average 5-10 operations.
One _operation_ can take an unspecified amount of time and real CPU cycles, depending on the particulars.
For example, loading a constant consumes very few CPU cycles, while calling an external Rust function,
though also counted as only one operation, may consume much more computing resources.
To help visualize, think of an _operation_ as roughly equals to one _instruction_ of a hypothetical CPU
which includes _specialized_ instructions, such as _function call_, _load module_ etc., each taking up
one CPU cycle to execute.

View File

@@ -0,0 +1,56 @@
Maximum Statement Depth
======================
{{#include ../links.md}}
Limiting How Deeply-Nested a Statement Can Be
--------------------------------------------
Rhai by default limits statements and expressions nesting to a maximum depth of 128
(which should be plenty) when they are at _global_ level, but only a depth of 32
when they are within function bodies.
For debug builds, these limits are set further downwards to 32 and 16 respectively.
That is because it is possible to overflow the [`Engine`]'s stack when it tries to
recursively parse an extremely deeply-nested code stream.
```rust
// The following, if long enough, can easily cause stack overflow during parsing.
let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(...)+1)))))))))));
```
This limit may be changed via the `Engine::set_max_expr_depths` method.
There are two limits to set, one for the maximum depth at global level, and the other for function bodies.
A script exceeding the maximum nesting depths will terminate with a parsing error.
The malicious `AST` will not be able to get past parsing in the first place.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_expr_depths(50, 5); // allow nesting up to 50 layers of expressions/statements
// at global level, but only 5 inside functions
```
Beware that there may be multiple layers for a simple language construct, even though it may correspond
to only one AST node. That is because the Rhai _parser_ internally runs a recursive chain of function calls
and it is important that a malicious script does not panic the parser in the first place.
Beware of Recursion
-------------------
_Functions_ are placed under stricter limits because of the multiplicative effect of _recursion_.
A script can effectively call itself while deep inside an expression chain within the function body,
thereby overflowing the stack even when the level of recursion is within limit.
In general, make sure that `C x ( 5 + F ) + S` layered calls do not cause a stack overflow, where:
* `C` = maximum call stack depth,
* `F` = maximum statement depth for functions,
* `S` = maximum statement depth at global level.

View File

@@ -0,0 +1,36 @@
Maximum Length of Strings
========================
{{#include ../links.md}}
Limiting How Long Strings Can Grow
---------------------------------
Rhai by default does not limit how long a [string] can be.
This can be changed via the `Engine::set_max_string_size` method, with zero being unlimited (the default).
A script attempting to create a string literal longer than the maximum length will terminate with a parse error.
Any script operation that produces a string longer than the maximum also terminates the script with an error result.
This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well).
```rust
let mut engine = Engine::new();
engine.set_max_string_size(500); // allow strings only up to 500 bytes long (in UTF-8 format)
engine.set_max_string_size(0); // allow unlimited string length
```
Setting Maximum Length
---------------------
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
a string's length without Rhai noticing until the very end.
For instance, the built-in '`+`' operator for strings concatenates two strings together to form one longer string;
if both strings are _slightly_ below the maximum length limit, the resultant string may be almost _twice_ the maximum length.

View File

@@ -0,0 +1,34 @@
Tracking Progress and Force-Termination
======================================
{{#include ../links.md}}
It is impossible to know when, or even whether, a script run will end
(a.k.a. the [Halting Problem](http://en.wikipedia.org/wiki/Halting_problem)).
When dealing with third-party untrusted scripts that may be malicious, to track evaluation progress and
to force-terminate a script prematurely (for any reason), provide a closure to the `Engine::on_progress` method:
```rust
let mut engine = Engine::new();
engine.on_progress(|&count| { // parameter is '&u64' - number of operations already performed
if count % 1000 == 0 {
println!("{}", count); // print out a progress log every 1,000 operations
}
true // return 'true' to continue running the script
// return 'false' to immediately terminate the script
});
```
The closure passed to `Engine::on_progress` will be called once for every operation.
Return `false` to terminate the script immediately.
Operations Count vs. Progress Percentage
---------------------------------------
Notice that the _operations count_ value passed into the closure does not indicate the _percentage_ of work
already done by the script (and thus it is not real _progress_ tracking), because it is impossible to determine
how long a script may run. It is possible, however, to calculate this percentage based on an estimated
total number of operations for a typical run.

17
doc/src/safety/sandbox.md Normal file
View File

@@ -0,0 +1,17 @@
Sand-Boxing - Block Access to External Data
==========================================
{{#include ../links.md}}
Rhai is _sand-boxed_ so a script can never read from outside its own environment.
Furthermore, an [`Engine`] created non-`mut` cannot mutate any state outside of itself;
so it is highly recommended that [`Engine`]'s are created immutable as much as possible.
```rust
let mut engine = Engine::new(); // create mutable 'Engine'
engine.register_get("add", add); // configure 'engine'
let engine = engine; // shadow the variable so that 'engine' is now immutable
```