rhai/eval/
data_check.rs

1//! Data size checks during evaluation.
2#![cfg(not(feature = "unchecked"))]
3
4use super::GlobalRuntimeState;
5use crate::types::dynamic::Union;
6use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR};
7use std::borrow::Borrow;
8#[cfg(feature = "no_std")]
9use std::prelude::v1::*;
10
11/// Recursively calculate the sizes of an array.
12///
13/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
14///
15/// # Panics
16///
17/// Panics if any interior data is shared (should never happen).
18#[cfg(not(feature = "no_index"))]
19#[inline]
20pub fn calc_array_sizes(array: &crate::Array) -> (usize, usize, usize) {
21    let (mut ax, mut mx, mut sx) = (0, 0, 0);
22
23    for value in array {
24        ax += 1;
25
26        match value.0 {
27            Union::Array(ref a, ..) => {
28                let (a, m, s) = calc_array_sizes(a);
29                ax += a;
30                mx += m;
31                sx += s;
32            }
33            Union::Blob(ref a, ..) => ax += 1 + a.len(),
34            #[cfg(not(feature = "no_object"))]
35            Union::Map(ref m, ..) => {
36                let (a, m, s) = calc_map_sizes(m);
37                ax += a;
38                mx += m;
39                sx += s;
40            }
41            Union::Str(ref s, ..) => sx += s.len(),
42            #[cfg(not(feature = "no_closure"))]
43            Union::Shared(..) => {
44                unreachable!("shared values discovered within data")
45            }
46            _ => (),
47        }
48    }
49
50    (ax, mx, sx)
51}
52/// Recursively calculate the sizes of a map.
53///
54/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
55///
56/// # Panics
57///
58/// Panics if any interior data is shared (should never happen).
59#[cfg(not(feature = "no_object"))]
60#[inline]
61pub fn calc_map_sizes(map: &crate::Map) -> (usize, usize, usize) {
62    let (mut ax, mut mx, mut sx) = (0, 0, 0);
63
64    for value in map.values() {
65        mx += 1;
66
67        match value.0 {
68            #[cfg(not(feature = "no_index"))]
69            Union::Array(ref a, ..) => {
70                let (a, m, s) = calc_array_sizes(a);
71                ax += a;
72                mx += m;
73                sx += s;
74            }
75            #[cfg(not(feature = "no_index"))]
76            Union::Blob(ref a, ..) => ax += 1 + a.len(),
77            Union::Map(ref m, ..) => {
78                let (a, m, s) = calc_map_sizes(m);
79                ax += a;
80                mx += m;
81                sx += s;
82            }
83            Union::Str(ref s, ..) => sx += s.len(),
84            #[cfg(not(feature = "no_closure"))]
85            Union::Shared(..) => {
86                unreachable!("shared values discovered within data")
87            }
88            _ => (),
89        }
90    }
91
92    (ax, mx, sx)
93}
94
95/// Recursively calculate the sizes of a value.
96///
97/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
98///
99/// # Panics
100///
101/// Panics if any interior data is shared (should never happen).
102#[inline]
103pub fn calc_data_sizes(value: &Dynamic, _top: bool) -> (usize, usize, usize) {
104    match value.0 {
105        #[cfg(not(feature = "no_index"))]
106        Union::Array(ref arr, ..) => calc_array_sizes(arr),
107        #[cfg(not(feature = "no_index"))]
108        Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
109        #[cfg(not(feature = "no_object"))]
110        Union::Map(ref map, ..) => calc_map_sizes(map),
111        Union::Str(ref s, ..) => (0, 0, s.len()),
112        #[cfg(not(feature = "no_closure"))]
113        Union::Shared(..) if _top => calc_data_sizes(&value.read_lock::<Dynamic>().unwrap(), true),
114        #[cfg(not(feature = "no_closure"))]
115        Union::Shared(..) => {
116            unreachable!("shared values discovered within data: {}", value)
117        }
118        _ => (0, 0, 0),
119    }
120}
121
122impl Engine {
123    /// Raise an error if any data size exceeds limit.
124    ///
125    /// [`Position`] in [`EvalAltResult`][crate::EvalAltResult] is always [`NONE`][Position::NONE]
126    /// and should be set afterwards.
127    #[cfg(not(feature = "unchecked"))]
128    pub(crate) fn throw_on_size(&self, (_arr, _map, s): (usize, usize, usize)) -> RhaiResultOf<()> {
129        if self.limits.string_len.map_or(false, |max| s > max.get()) {
130            return Err(
131                ERR::ErrorDataTooLarge("Length of string".to_string(), Position::NONE).into(),
132            );
133        }
134
135        #[cfg(not(feature = "no_index"))]
136        if self.limits.array_size.map_or(false, |max| _arr > max.get()) {
137            return Err(
138                ERR::ErrorDataTooLarge("Size of array/BLOB".to_string(), Position::NONE).into(),
139            );
140        }
141
142        #[cfg(not(feature = "no_object"))]
143        if self.limits.map_size.map_or(false, |max| _map > max.get()) {
144            return Err(
145                ERR::ErrorDataTooLarge("Size of object map".to_string(), Position::NONE).into(),
146            );
147        }
148
149        Ok(())
150    }
151
152    /// Check whether the size of a [`Dynamic`] is within limits.
153    #[cfg(not(feature = "unchecked"))]
154    #[inline]
155    pub(crate) fn check_data_size<T: Borrow<Dynamic>>(
156        &self,
157        value: T,
158        pos: Position,
159    ) -> RhaiResultOf<T> {
160        // If no data size limits, just return
161        if !self.has_data_size_limit() {
162            return Ok(value);
163        }
164
165        let sizes = calc_data_sizes(value.borrow(), true);
166
167        self.throw_on_size(sizes)
168            .map_err(|err| err.fill_position(pos))?;
169
170        Ok(value)
171    }
172
173    /// Raise an error if the size of a [`Dynamic`] is out of limits (if any).
174    ///
175    /// Not available under `unchecked`.
176    #[cfg(not(feature = "unchecked"))]
177    #[inline(always)]
178    pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> {
179        self.check_data_size(value, Position::NONE).map(|_| ())
180    }
181
182    /// Check if the number of operations stay within limit.
183    #[inline(always)]
184    pub(crate) fn track_operation(
185        &self,
186        global: &mut GlobalRuntimeState,
187        pos: Position,
188    ) -> RhaiResultOf<()> {
189        global.num_operations += 1;
190
191        // Guard against too many operations
192        #[cfg(not(feature = "unchecked"))]
193        if self.max_operations() > 0 && global.num_operations > self.max_operations() {
194            return Err(ERR::ErrorTooManyOperations(pos).into());
195        }
196
197        self.progress
198            .as_ref()
199            .and_then(|progress| {
200                progress(global.num_operations)
201                    .map(|token| Err(ERR::ErrorTerminated(token, pos).into()))
202            })
203            .unwrap_or(Ok(()))
204    }
205}