diff --git a/src/engine.rs b/src/engine.rs index 7f34733a..37f279d2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -135,7 +135,8 @@ impl Imports { .rev() .find_map(|(_, m)| m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))) } - /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]? + /// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of + /// imported [modules][Module]? #[allow(dead_code)] #[inline(always)] pub fn contains_iter(&self, id: TypeId) -> bool { @@ -508,8 +509,8 @@ pub struct State { /// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned. /// When that happens, this flag is turned on to force a scope lookup by name. pub always_search: bool, - /// Level of the current scope. The global (root) level is zero, a new block (or function call) - /// is one level higher, and so on. + /// Level of the current scope. The global (root) level is zero, a new block + /// (or function call) is one level higher, and so on. pub scope_level: usize, /// Number of operations performed. pub operations: u64, @@ -542,30 +543,34 @@ impl State { pub struct Limits { /// Maximum levels of call-stack to prevent infinite recursion. /// Not available under `no_function`. + /// + /// Set to zero to effectively disable function calls. #[cfg(not(feature = "no_function"))] pub max_call_stack_depth: usize, - /// Maximum depth of statements/expressions at global level (0 = unlimited). - pub max_expr_depth: usize, - /// Maximum depth of statements/expressions in functions (0 = unlimited). + /// Maximum depth of statements/expressions at global level. + pub max_expr_depth: Option, + /// Maximum depth of statements/expressions in functions. /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] - pub max_function_expr_depth: usize, - /// Maximum number of operations allowed to run (0 = unlimited). - pub max_operations: u64, + pub max_function_expr_depth: Option, + /// Maximum number of operations allowed to run. + pub max_operations: Option, /// Maximum number of [modules][Module] allowed to load. /// Not available under `no_module`. + /// + /// Set to zero to effectively disable loading any [module][Module]. #[cfg(not(feature = "no_module"))] pub max_modules: usize, - /// Maximum length of a [string][ImmutableString] (0 = unlimited). - pub max_string_size: usize, - /// Maximum length of an [array][Array] (0 = unlimited). + /// Maximum length of a [string][ImmutableString]. + pub max_string_size: Option, + /// Maximum length of an [array][Array]. /// Not available under `no_index`. #[cfg(not(feature = "no_index"))] - pub max_array_size: usize, - /// Maximum number of properties in an [object map][Map] (0 = unlimited). + pub max_array_size: Option, + /// Maximum number of properties in an [object map][Map]. /// Not available under `no_object`. #[cfg(not(feature = "no_object"))] - pub max_map_size: usize, + pub max_map_size: Option, } /// Context of a script evaluation process. @@ -777,13 +782,13 @@ pub fn search_imports( // Qualified - check if the root module is directly indexed let index = if state.always_search { - 0 + None } else { - namespace.index().map_or(0, NonZeroUsize::get) + namespace.index() }; - Ok(if index > 0 { - let offset = mods.len() - index; + Ok(if let Some(index) = index { + let offset = mods.len() - index.get(); mods.get(offset).expect("invalid index in Imports") } else { mods.find(root) @@ -838,17 +843,17 @@ impl Engine { limits: Limits { #[cfg(not(feature = "no_function"))] max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, + max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, + max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH), + max_operations: None, #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, - max_string_size: 0, + max_string_size: None, #[cfg(not(feature = "no_index"))] - max_array_size: 0, + max_array_size: None, #[cfg(not(feature = "no_object"))] - max_map_size: 0, + max_map_size: None, }, disable_doc_comments: false, @@ -895,17 +900,17 @@ impl Engine { limits: Limits { #[cfg(not(feature = "no_function"))] max_call_stack_depth: MAX_CALL_STACK_DEPTH, - max_expr_depth: MAX_EXPR_DEPTH, + max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH), #[cfg(not(feature = "no_function"))] - max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: 0, + max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH), + max_operations: None, #[cfg(not(feature = "no_module"))] max_modules: usize::MAX, - max_string_size: 0, + max_string_size: None, #[cfg(not(feature = "no_index"))] - max_array_size: 0, + max_array_size: None, #[cfg(not(feature = "no_object"))] - max_map_size: 0, + max_map_size: None, }, disable_doc_comments: false, @@ -975,14 +980,11 @@ impl Engine { } // Check if it is directly indexed - let index = if state.always_search { - 0 - } else { - index.map_or(0, NonZeroUsize::get) - }; + let index = if state.always_search { &None } else { index }; // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { + let index = index.map(NonZeroUsize::get).unwrap_or(0); let context = EvalContext { engine: self, scope, @@ -1000,8 +1002,8 @@ impl Engine { } } - let index = if index > 0 { - scope.len() - index + let index = if let Some(index) = index { + scope.len() - index.get() } else { // Find the variable in the scope scope @@ -1012,8 +1014,8 @@ impl Engine { let val = scope.get_mut_by_index(index); - // Check for data race - probably not necessary because the only place it should conflict is in a method call - // when the object variable is also used as a parameter. + // Check for data race - probably not necessary because the only place it should conflict is + // in a method call when the object variable is also used as a parameter. // if cfg!(not(feature = "no_closure")) && val.is_locked() { // return EvalAltResult::ErrorDataRace(name.into(), *pos).into(); // } @@ -1285,7 +1287,8 @@ impl Engine { ) .or_else( |err| match *err { - // If there is no setter, no need to feed it back because the property is read-only + // If there is no setter, no need to feed it back because + // the property is read-only EvalAltResult::ErrorDotExpr(_, _) => { Ok((Dynamic::UNIT, false)) } @@ -1405,7 +1408,8 @@ impl Engine { } /// Evaluate a chain of indexes and store the results in a [`StaticVec`]. - /// [`StaticVec`] is used to avoid an allocation in the overwhelming cases of just a few levels of indexing. + /// [`StaticVec`] is used to avoid an allocation in the overwhelming cases of + /// just a few levels of indexing. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_indexed_chain( &self, @@ -2400,20 +2404,23 @@ impl Engine { result: Result>, pos: Position, ) -> Result> { - // If no data size limits, just return - let mut total = 0; + // Simply return all errors + if result.is_err() { + return result; + } - total += self.max_string_size(); + // If no data size limits, just return + let mut has_limit = self.limits.max_string_size.is_some(); #[cfg(not(feature = "no_index"))] { - total += self.max_array_size(); + has_limit = has_limit || self.limits.max_array_size.is_some(); } #[cfg(not(feature = "no_object"))] { - total += self.max_map_size(); + has_limit = has_limit || self.limits.max_map_size.is_some(); } - if total == 0 { + if !has_limit { return result; } @@ -2469,34 +2476,33 @@ impl Engine { } } - match result { - // Simply return all errors - Err(_) => return result, - // String with limit - Ok(Dynamic(Union::Str(_, _))) if self.max_string_size() > 0 => (), - // Array with limit - #[cfg(not(feature = "no_index"))] - Ok(Dynamic(Union::Array(_, _))) if self.max_array_size() > 0 => (), - // Map with limit - #[cfg(not(feature = "no_object"))] - Ok(Dynamic(Union::Map(_, _))) if self.max_map_size() > 0 => (), - // Everything else is simply returned - Ok(_) => return result, - }; - let (_arr, _map, s) = calc_size(result.as_ref().unwrap()); - if s > self.max_string_size() { + if s > self + .limits + .max_string_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), pos).into(); } #[cfg(not(feature = "no_index"))] - if _arr > self.max_array_size() { + if _arr + > self + .limits + .max_array_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), pos).into(); } #[cfg(not(feature = "no_object"))] - if _map > self.max_map_size() { + if _map + > self + .limits + .max_map_size + .map_or(usize::MAX, NonZeroUsize::get) + { return EvalAltResult::ErrorDataTooLarge("Size of object map".to_string(), pos).into(); } diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 7d99c1fa..e44d8d30 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -1,6 +1,10 @@ //! Configuration settings for [`Engine`]. -use crate::stdlib::{format, num::NonZeroU8, string::String}; +use crate::stdlib::{ + format, + num::{NonZeroU64, NonZeroU8, NonZeroUsize}, + string::String, +}; use crate::token::Token; use crate::Engine; @@ -62,11 +66,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_operations(&mut self, operations: u64) -> &mut Self { - self.limits.max_operations = if operations == u64::MAX { - 0 - } else { - operations - }; + self.limits.max_operations = NonZeroU64::new(operations); self } /// The maximum number of operations allowed for a script to run (0 for unlimited). @@ -75,7 +75,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_operations(&self) -> u64 { - self.limits.max_operations + self.limits.max_operations.map_or(0, NonZeroU64::get) } /// Set the maximum number of imported [modules][crate::Module] allowed for a script. /// @@ -106,18 +106,10 @@ impl Engine { max_expr_depth: usize, #[cfg(not(feature = "no_function"))] max_function_expr_depth: usize, ) -> &mut Self { - self.limits.max_expr_depth = if max_expr_depth == usize::MAX { - 0 - } else { - max_expr_depth - }; + self.limits.max_expr_depth = NonZeroUsize::new(max_expr_depth); #[cfg(not(feature = "no_function"))] { - self.limits.max_function_expr_depth = if max_function_expr_depth == usize::MAX { - 0 - } else { - max_function_expr_depth - }; + self.limits.max_function_expr_depth = NonZeroUsize::new(max_function_expr_depth); } self } @@ -127,7 +119,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_expr_depth(&self) -> usize { - self.limits.max_expr_depth + self.limits.max_expr_depth.map_or(0, NonZeroUsize::get) } /// The depth limit for expressions in functions (0 for unlimited). /// @@ -136,7 +128,9 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn max_function_expr_depth(&self) -> usize { - self.limits.max_function_expr_depth + self.limits + .max_function_expr_depth + .map_or(0, NonZeroUsize::get) } /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// @@ -144,7 +138,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn set_max_string_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_string_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_string_size = NonZeroUsize::new(max_size); self } /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). @@ -153,7 +147,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn max_string_size(&self) -> usize { - self.limits.max_string_size + self.limits.max_string_size.map_or(0, NonZeroUsize::get) } /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). /// @@ -162,7 +156,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn set_max_array_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_array_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_array_size = NonZeroUsize::new(max_size); self } /// The maximum length of [arrays][crate::Array] (0 for unlimited). @@ -172,7 +166,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn max_array_size(&self) -> usize { - self.limits.max_array_size + self.limits.max_array_size.map_or(0, NonZeroUsize::get) } /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// @@ -181,7 +175,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_max_map_size(&mut self, max_size: usize) -> &mut Self { - self.limits.max_map_size = if max_size == usize::MAX { 0 } else { max_size }; + self.limits.max_map_size = NonZeroUsize::new(max_size); self } /// The maximum size of [object maps][crate::Map] (0 for unlimited). @@ -191,7 +185,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn max_map_size(&self) -> usize { - self.limits.max_map_size + self.limits.max_map_size.map_or(0, NonZeroUsize::get) } /// Set the module resolution service used by the [`Engine`]. /// diff --git a/src/parser.rs b/src/parser.rs index a86095ac..4c462f66 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -61,11 +61,11 @@ struct ParseState<'e> { modules: StaticVec, /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] - max_expr_depth: usize, + max_expr_depth: Option, /// Maximum levels of expression nesting in functions. #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - max_function_expr_depth: usize, + max_function_expr_depth: Option, } impl<'e> ParseState<'e> { @@ -73,10 +73,10 @@ impl<'e> ParseState<'e> { #[inline(always)] pub fn new( engine: &'e Engine, - #[cfg(not(feature = "unchecked"))] max_expr_depth: usize, + #[cfg(not(feature = "unchecked"))] max_expr_depth: Option, #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - max_function_expr_depth: usize, + max_function_expr_depth: Option, ) -> Self { Self { engine, @@ -212,15 +212,17 @@ impl ParseSettings { } /// Make sure that the current level of expression nesting is within the maximum limit. #[cfg(not(feature = "unchecked"))] - #[inline] - pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { - if limit == 0 { - Ok(()) - } else if self.level > limit { - Err(PERR::ExprTooDeep.into_err(self.pos)) - } else { - Ok(()) + #[inline(always)] + pub fn ensure_level_within_max_limit( + &self, + limit: Option, + ) -> Result<(), ParseError> { + if let Some(limit) = limit { + if self.level > limit.get() { + return Err(PERR::ExprTooDeep.into_err(self.pos)); + } } + Ok(()) } } @@ -3040,10 +3042,10 @@ impl Engine { let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] - self.max_expr_depth(), + NonZeroUsize::new(self.max_expr_depth()), #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - self.max_function_expr_depth(), + NonZeroUsize::new(self.max_function_expr_depth()), ); let settings = ParseSettings { @@ -3087,10 +3089,10 @@ impl Engine { let mut state = ParseState::new( self, #[cfg(not(feature = "unchecked"))] - self.max_expr_depth(), + NonZeroUsize::new(self.max_expr_depth()), #[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_function"))] - self.max_function_expr_depth(), + NonZeroUsize::new(self.max_function_expr_depth()), ); while !input.peek().unwrap().0.is_eof() { diff --git a/src/token.rs b/src/token.rs index 7ae8ba8a..b08339d4 100644 --- a/src/token.rs +++ b/src/token.rs @@ -8,6 +8,7 @@ use crate::stdlib::{ borrow::Cow, char, fmt, format, iter::Peekable, + num::NonZeroUsize, str::{Chars, FromStr}, string::{String, ToString}, }; @@ -747,7 +748,7 @@ impl From for String { #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string (0 = unlimited). - pub max_string_size: usize, + pub max_string_size: Option, /// Can the next token be a unary operator? pub non_unary: bool, /// Is the tokenizer currently inside a block comment? @@ -796,8 +797,10 @@ pub fn parse_string_literal( pos.advance(); - if state.max_string_size > 0 && result.len() > state.max_string_size { - return Err((LexError::StringTooLong(state.max_string_size), *pos)); + if let Some(max) = state.max_string_size { + if result.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), *pos)); + } } match next_char { @@ -902,8 +905,10 @@ pub fn parse_string_literal( let s = result.iter().collect::(); - if state.max_string_size > 0 && s.len() > state.max_string_size { - return Err((LexError::StringTooLong(state.max_string_size), *pos)); + if let Some(max) = state.max_string_size { + if s.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), *pos)); + } } Ok(s)