rhai 1.12.0

Embedded scripting for Rust
Documentation
//! Data size checks during evaluation.
#![cfg(not(feature = "unchecked"))]

use super::GlobalRuntimeState;
use crate::types::dynamic::Union;
use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR};
use std::borrow::Borrow;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

impl Dynamic {
    /// Recursively calculate the sizes of an array.
    ///
    /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
    ///
    /// # Panics
    ///
    /// Panics if any interior data is shared (should never happen).
    #[cfg(not(feature = "no_index"))]
    #[inline]
    pub(crate) fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
        let (mut ax, mut mx, mut sx) = (0, 0, 0);

        for value in array {
            ax += 1;

            match value.0 {
                Union::Array(ref a, ..) => {
                    let (a, m, s) = Self::calc_array_sizes(a);
                    ax += a;
                    mx += m;
                    sx += s;
                }
                Union::Blob(ref a, ..) => ax += 1 + a.len(),
                #[cfg(not(feature = "no_object"))]
                Union::Map(ref m, ..) => {
                    let (a, m, s) = Self::calc_map_sizes(m);
                    ax += a;
                    mx += m;
                    sx += s;
                }
                Union::Str(ref s, ..) => sx += s.len(),
                #[cfg(not(feature = "no_closure"))]
                Union::Shared(..) => {
                    unreachable!("shared values discovered within data")
                }
                _ => (),
            }
        }

        (ax, mx, sx)
    }
    /// Recursively calculate the sizes of a map.
    ///
    /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
    ///
    /// # Panics
    ///
    /// Panics if any interior data is shared (should never happen).
    #[cfg(not(feature = "no_object"))]
    #[inline]
    pub(crate) fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
        let (mut ax, mut mx, mut sx) = (0, 0, 0);

        for value in map.values() {
            mx += 1;

            match value.0 {
                #[cfg(not(feature = "no_index"))]
                Union::Array(ref a, ..) => {
                    let (a, m, s) = Self::calc_array_sizes(a);
                    ax += a;
                    mx += m;
                    sx += s;
                }
                #[cfg(not(feature = "no_index"))]
                Union::Blob(ref a, ..) => ax += 1 + a.len(),
                Union::Map(ref m, ..) => {
                    let (a, m, s) = Self::calc_map_sizes(m);
                    ax += a;
                    mx += m;
                    sx += s;
                }
                Union::Str(ref s, ..) => sx += s.len(),
                #[cfg(not(feature = "no_closure"))]
                Union::Shared(..) => {
                    unreachable!("shared values discovered within data")
                }
                _ => (),
            }
        }

        (ax, mx, sx)
    }
    /// Recursively calculate the sizes of a value.
    ///
    /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
    ///
    /// # Panics
    ///
    /// Panics if any interior data is shared (should never happen).
    #[inline]
    pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
        match self.0 {
            #[cfg(not(feature = "no_index"))]
            Union::Array(ref arr, ..) => Self::calc_array_sizes(&**arr),
            #[cfg(not(feature = "no_index"))]
            Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
            #[cfg(not(feature = "no_object"))]
            Union::Map(ref map, ..) => Self::calc_map_sizes(&**map),
            Union::Str(ref s, ..) => (0, 0, s.len()),
            #[cfg(not(feature = "no_closure"))]
            Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true),
            #[cfg(not(feature = "no_closure"))]
            Union::Shared(..) => {
                unreachable!("shared values discovered within data: {}", self)
            }
            _ => (0, 0, 0),
        }
    }
}

impl Engine {
    /// Raise an error if any data size exceeds limit.
    ///
    /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE]
    /// and should be set afterwards.
    pub(crate) fn throw_on_size(&self, (_arr, _map, s): (usize, usize, usize)) -> RhaiResultOf<()> {
        if self
            .limits
            .max_string_len
            .map_or(false, |max| s > max.get())
        {
            return Err(
                ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(),
            );
        }

        #[cfg(not(feature = "no_index"))]
        if self
            .limits
            .max_array_size
            .map_or(false, |max| _arr > max.get())
        {
            return Err(
                ERR::ErrorDataTooLarge("Size of array/BLOB".to_string(), Position::NONE).into(),
            );
        }

        #[cfg(not(feature = "no_object"))]
        if self
            .limits
            .max_map_size
            .map_or(false, |max| _map > max.get())
        {
            return Err(
                ERR::ErrorDataTooLarge("Size of object map".to_string(), Position::NONE).into(),
            );
        }

        Ok(())
    }

    /// Check whether the size of a [`Dynamic`] is within limits.
    #[inline]
    pub(crate) fn check_data_size<T: Borrow<Dynamic>>(
        &self,
        value: T,
        pos: Position,
    ) -> RhaiResultOf<T> {
        // If no data size limits, just return
        if !self.has_data_size_limit() {
            return Ok(value);
        }

        let sizes = value.borrow().calc_data_sizes(true);

        self.throw_on_size(sizes)
            .map_err(|err| err.fill_position(pos))?;

        Ok(value)
    }

    /// Raise an error if the size of a [`Dynamic`] is out of limits (if any).
    ///
    /// Not available under `unchecked`.
    #[inline(always)]
    pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> {
        self.check_data_size(value, Position::NONE).map(|_| ())
    }

    /// Check if the number of operations stay within limit.
    pub(crate) fn track_operation(
        &self,
        global: &mut GlobalRuntimeState,
        pos: Position,
    ) -> RhaiResultOf<()> {
        global.num_operations += 1;

        // Guard against too many operations
        let max = self.max_operations();

        if max > 0 && global.num_operations > max {
            return Err(ERR::ErrorTooManyOperations(pos).into());
        }

        // Report progress
        self.progress
            .as_ref()
            .and_then(|p| p(global.num_operations))
            .map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into()))
    }
}