This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
rhaj/_archive/rhai_engine/rhaibook/safety/max-stmt-depth.md
2025-04-04 08:28:07 +02:00

2.8 KiB

Maximum Expression Nesting Depth

{{#include ../links.md}}

Rhai by default limits statement and expression nesting to a maximum depth of 64 (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.

// 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 [Engine::set_max_expr_depths][options].

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 parse 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).

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
Multiple layers of expressions may be generated 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.

_[Functions]_ are placed under stricter limits because of the multiplicative effect of _recursion_.

A [function] 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.

```rust
fn deep_calc(a, n) {
    (a+(a+(a+(a+(a+(a+(a+(a+(a+ ... (a+deep_calc(a,n+1)) ... )))))))))
    //                                 ^^^^^^^^^^^^^^^^ recursive call!
}

let a = 42;

let result = (a+(a+(a+(a+(a+(a+(a+(a+(a+ ... (a+deep_calc(a,0)) ... )))))))));
```

In the contrived example above, each recursive call to the [function] `deep_calc` adds the total
number of nested expression layers to Rhai's evaluation stack.  Sooner or later (most likely sooner
than the limit for [maximum depth of function calls][maximum call stack depth] is reached), a stack
overflow can be expected.

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.