Resource Limits

Risor provides three mechanisms to limit script execution: instruction count limits, call stack depth limits, and timeouts. Use them when running untrusted or user-provided scripts.

Instruction Limit

Use WithMaxSteps to cap the total number of bytecode instructions the VM will execute. This prevents infinite recursion and runaway computations.

result, err := risor.Eval(ctx, source, risor.WithMaxSteps(10000))

When the limit is exceeded, the error wraps risor.ErrStepLimitExceeded:

result, err := risor.Eval(ctx, source, risor.WithMaxSteps(10000))
if errors.Is(err, risor.ErrStepLimitExceeded) {
    log.Println("script exceeded instruction limit")
}

A value of 0 means unlimited (the default).

Stack Depth Limit

Use WithMaxStackDepth to limit how deeply functions can nest. This catches infinite recursion that might otherwise exhaust memory.

result, err := risor.Eval(ctx, source, risor.WithMaxStackDepth(100))

When the limit is exceeded, the error wraps risor.ErrStackOverflow:

result, err := risor.Eval(ctx, source, risor.WithMaxStackDepth(100))
if errors.Is(err, risor.ErrStackOverflow) {
    log.Println("script exceeded stack depth limit")
}

Timeout

Use WithTimeout to set a wall-clock deadline for script execution. This is a hard limit. When the timeout expires, execution is cancelled via the Go context.

result, err := risor.Eval(ctx, source,
    risor.WithTimeout(100 * time.Millisecond),
)

When the timeout is exceeded, the error is context.DeadlineExceeded:

result, err := risor.Eval(ctx, source,
    risor.WithTimeout(100 * time.Millisecond),
)
if errors.Is(err, context.DeadlineExceeded) {
    log.Println("script timed out")
}

Combining Limits

All three limits can be used together. The first limit reached will terminate execution.

result, err := risor.Eval(ctx, source,
    risor.WithEnv(risor.Builtins()),
    risor.WithMaxSteps(50000),
    risor.WithMaxStackDepth(50),
    risor.WithTimeout(5 * time.Second),
)

Error Handling

Each limit produces a distinct error type, making it straightforward to identify which limit was hit:

LimitError
Instruction countrisor.ErrStepLimitExceeded
Stack depthrisor.ErrStackOverflow
Timeoutcontext.DeadlineExceeded

Use errors.Is to check for these errors:

switch {
case errors.Is(err, risor.ErrStepLimitExceeded):
    // too many instructions
case errors.Is(err, risor.ErrStackOverflow):
    // recursion too deep
case errors.Is(err, context.DeadlineExceeded):
    // wall-clock timeout
}

Recommendations

  • Untrusted scripts. Always set all three limits. A step limit of 100,000 and a timeout of 5 seconds are reasonable starting points.
  • Internal scripts. You may only need a timeout to guard against bugs.
  • Expression evaluation. If you use WithSyntax(risor.ExpressionOnly), a modest step limit (e.g. 10,000) is usually sufficient since expressions cannot define recursive functions.

Risor © 2026 Deep Noodle