Add Rhai book.
This commit is contained in:
11
doc/src/safety/checked.md
Normal file
11
doc/src/safety/checked.md
Normal 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).
|
40
doc/src/safety/max-array-size.md
Normal file
40
doc/src/safety/max-array-size.md
Normal 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).
|
31
doc/src/safety/max-call-stack.md
Normal file
31
doc/src/safety/max-call-stack.md
Normal 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]).
|
40
doc/src/safety/max-map-size.md
Normal file
40
doc/src/safety/max-map-size.md
Normal 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).
|
24
doc/src/safety/max-modules.md
Normal file
24
doc/src/safety/max-modules.md
Normal 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
|
||||
```
|
43
doc/src/safety/max-operations.md
Normal file
43
doc/src/safety/max-operations.md
Normal 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.
|
56
doc/src/safety/max-stmt-depth.md
Normal file
56
doc/src/safety/max-stmt-depth.md
Normal 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.
|
36
doc/src/safety/max-string-size.md
Normal file
36
doc/src/safety/max-string-size.md
Normal 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.
|
||||
|
34
doc/src/safety/progress.md
Normal file
34
doc/src/safety/progress.md
Normal 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
17
doc/src/safety/sandbox.md
Normal 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
|
||||
```
|
Reference in New Issue
Block a user