ketos 0.12.0

Lisp dialect scripting and extension language
Documentation
//! Configuration of runtime execution restrictions
//!
//! The `RestrictConfig` instance within an execution context configures
//! the restrictions imposed on code execution. These restrictions may include
//! limiting execution time and memory consumption.
//!
//! However, use of `RestrictConfig` is not necessarily sufficient to isolate
//! an execution environment from the host system. Other steps should be taken,
//! such as:
//!
//! * Installing a `GlobalIo` instance that does not allow access to
//!   system `stdout` and `stdin` streams.
//! * Installing a `ModuleLoader` instance that restricts access to the
//!  filesystem and whitelists a small set of "safe" builtin modules.
//!
//! # Example
//!
//! ```
//! use std::rc::Rc;
//! use ketos::{Builder, GlobalIo, BuiltinModuleLoader, RestrictConfig};
//!
//! let interp = Builder::new()
//!     .restrict(RestrictConfig::strict())
//!     .io(Rc::new(GlobalIo::null()))
//!     .module_loader(Box::new(BuiltinModuleLoader))
//!     .finish();
//!
//! // ...
//! # let _ = interp;
//! ```

use std::fmt;
use std::time::Duration;

use crate::name::{NameDisplay, NameStore};

/// Contains parameters configuring restrictions of runtime code execution
///
/// See [module-level documentation](index.html) for an example of its use.
#[derive(Clone, Debug)]
pub struct RestrictConfig {
    /// Limits the maximum execution time, beginning from a call into the
    /// virtual machine, until the topmost function returns.
    ///
    /// If the user desires to limit total execution time of multiple separate
    /// function calls, the user must track execution time and adjust this
    /// limit manually.
    pub execution_time: Option<Duration>,
    /// Limits the call stack depth during execution to a number of nested
    /// functions calls
    pub call_stack_size: usize,
    /// Limits the number of values that can be stored on the stack during
    /// execution
    pub value_stack_size: usize,
    /// Limits the maximum number of values that can be stored in a `GlobalScope`
    pub namespace_size: usize,
    /// Memory limit during execution of code.
    /// This is not a specific measure of bytes; it's more an abstract
    /// estimate of values held during execution.
    pub memory_limit: usize,
    /// Maximum size, in bits, of integer and ratio values
    pub max_integer_size: usize,
    /// Maximum nested depth of syntactical elements
    pub max_syntax_nesting: usize,
}

/// Represents an error caused by breach of runtime execution restrictions
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum RestrictError {
    /// Execution time exceeded limit
    ExecutionTimeExceeded,
    /// Call stack exceeded limit
    CallStackExceeded,
    /// Value stack exceeded limit
    ValueStackExceeded,
    /// Namespace size exceeded limit
    NamespaceSizeExceeded,
    /// Memory consumption exceeded limit
    MemoryLimitExceeded,
    /// Integer bit limit exceeded
    IntegerLimitExceeded,
    /// Nested syntax exceeded limit
    MaxSyntaxNestingExceeded,
}

impl RestrictError {
    /// Returns a string describing the error that occurred.
    pub fn description(self) -> &'static str {
        use self::RestrictError::*;

        match self {
            ExecutionTimeExceeded => "execution time exceeded",
            CallStackExceeded => "max call stack exceeded",
            ValueStackExceeded => "max value stack exceeded",
            NamespaceSizeExceeded => "max namespace size exceeded",
            MemoryLimitExceeded => "max memory limit exceeded",
            IntegerLimitExceeded => "integer size limit exceeded",
            MaxSyntaxNestingExceeded => "max syntax nesting exceeded",
        }
    }
}

impl fmt::Display for RestrictError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.description())
    }
}

impl NameDisplay for RestrictError {
    fn fmt(&self, _names: &NameStore, f: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

/// Maximum size of call stack, with permissive configuration.
pub const PERMISSIVE_CALL_STACK_SIZE: usize = 1024;

/// Maximum size of value stack, in values, with permissive configuration.
pub const PERMISSIVE_VALUE_STACK_SIZE: usize = 4096;

/// Maximum size of call stack, with strict configuration.
pub const STRICT_CALL_STACK_SIZE: usize = PERMISSIVE_CALL_STACK_SIZE / 16;

/// Maximum size of value stack, in values, with strict configuration.
pub const STRICT_VALUE_STACK_SIZE: usize = PERMISSIVE_VALUE_STACK_SIZE / 16;

impl RestrictConfig {
    /// Returns a `RestrictConfig` that is most permissive.
    ///
    /// No restrictions are placed on executing code.
    pub fn permissive() -> RestrictConfig {
        RestrictConfig{
            execution_time: None,
            call_stack_size: PERMISSIVE_CALL_STACK_SIZE,
            value_stack_size: PERMISSIVE_VALUE_STACK_SIZE,
            namespace_size: usize::max_value(),
            memory_limit: usize::max_value(),
            max_integer_size: usize::max_value(),
            max_syntax_nesting: usize::max_value(),
        }
    }

    /// Returns a `RestrictConfig` that is most strict.
    ///
    /// Small programs with short runtimes should not have a problem operating
    /// within these restrictions.
    pub fn strict() -> RestrictConfig {
        RestrictConfig{
            execution_time: Some(Duration::from_millis(100)),
            call_stack_size: STRICT_CALL_STACK_SIZE,
            value_stack_size: STRICT_VALUE_STACK_SIZE,
            namespace_size: 32,
            memory_limit: STRICT_VALUE_STACK_SIZE,
            max_integer_size: 100,
            max_syntax_nesting: 32,
        }
    }
}