diff --git a/README.md b/README.md index 3ab88d26..78fbae94 100644 --- a/README.md +++ b/README.md @@ -2207,7 +2207,7 @@ let a = new_ts(); // constructor function a.field = 500; // property setter a.update(); // method call, 'a' can be modified -update(a); // <- this de-sugars to 'a.update()' this if 'a' is a simple variable +update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple variable // unlike scripted functions, 'a' can be modified and is not a copy let array = [ a ]; @@ -2480,11 +2480,16 @@ engine.set_max_string_size(500); // allow strings only up to 500 byte engine.set_max_string_size(0); // allow unlimited string length ``` -A script attempting to create a string literal longer than the maximum will terminate with a parse error. +A script attempting to create a string literal longer than the maximum length will terminate with a parse error. Any script operation that produces a string longer than the maximum also terminates the script with an error result. This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +a string's length without Rhai noticing until the very end. For instance, the built-in '`+`' operator for strings +concatenates two strings together to form one longer string; if both strings are _slightly_ below the maximum +length limit, the resultant string may be almost _twice_ the maximum length. + ### Maximum size of arrays Rhai by default does not limit how large an [array] can be. @@ -2503,6 +2508,16 @@ Any script operation that produces an array larger than the maximum also termina This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +an array's size without Rhai noticing until the very end. +For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array; +if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size. + +As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual +array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays +and object maps contained within each array to make sure that the _aggregate_ sizes of none of these data structures +exceed their respective maximum size limits (if any). + ### Maximum size of object maps Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be. @@ -2521,6 +2536,16 @@ Any script operation that produces an object map with more properties than the m This check can be disabled via the [`unchecked`] feature for higher performance (but higher risks as well). +Be conservative when setting a maximum limit and always consider the fact that a registered function may grow +an object map's size without Rhai noticing until the very end. For instance, the built-in '`+`' operator for object maps +concatenates two object maps together to form one larger object map; if both object maps are _slightly_ below the maximum +size limit, the resultant object map may be almost _twice_ the maximum size. + +As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual +object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays +and object maps contained within each object map to make sure that the _aggregate_ sizes of none of these data structures +exceed their respective maximum size limits (if any). + ### Maximum number of operations Rhai by default does not limit how much time or CPU a script consumes. @@ -2582,14 +2607,17 @@ total number of operations for a typical run. ### Maximum number of modules Rhai by default does not limit how many [modules] can be loaded via [`import`] statements. -This can be changed via the `Engine::set_max_modules` method, with zero being unlimited (the default). +This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number +of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether. ```rust let mut engine = Engine::new(); engine.set_max_modules(5); // allow loading only up to 5 modules -engine.set_max_modules(0); // allow unlimited modules +engine.set_max_modules(0); // disallow loading any module (maximum = zero) + +engine.set_max_modules(1000); // set to a large number for effectively unlimited modules ``` A script attempting to load more than the maximum number of modules will terminate with an error result. diff --git a/src/api.rs b/src/api.rs index 56adc08c..6a6dbf5c 100644 --- a/src/api.rs +++ b/src/api.rs @@ -751,7 +751,6 @@ impl Engine { ) -> Result { let scripts = [script]; let stream = lex(&scripts, self.max_string_size); - { let mut peekable = stream.peekable(); self.parse_global_expr(&mut peekable, scope, self.optimization_level) @@ -906,11 +905,8 @@ impl Engine { let scripts = [script]; let stream = lex(&scripts, self.max_string_size); - let ast = self.parse_global_expr( - &mut stream.peekable(), - scope, - OptimizationLevel::None, // No need to optimize a lone expression - )?; + // No need to optimize a lone expression + let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; self.eval_ast_with_scope(scope, &ast) } @@ -983,6 +979,7 @@ impl Engine { }); } + /// Evaluate an `AST` with own scope. pub(crate) fn eval_ast_with_scope_raw( &self, scope: &mut Scope, @@ -1035,7 +1032,6 @@ impl Engine { ) -> Result<(), Box> { let scripts = [script]; let stream = lex(&scripts, self.max_string_size); - let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } diff --git a/src/engine.rs b/src/engine.rs index 6759c787..79924129 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -63,16 +63,9 @@ pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; #[cfg(feature = "unchecked")] pub const MAX_CALL_STACK_DEPTH: usize = usize::MAX; #[cfg(feature = "unchecked")] -pub const MAX_EXPR_DEPTH: usize = usize::MAX; +pub const MAX_EXPR_DEPTH: usize = 0; #[cfg(feature = "unchecked")] -pub const MAX_FUNCTION_EXPR_DEPTH: usize = usize::MAX; - -#[cfg(feature = "unchecked")] -pub const MAX_STRING_SIZE: usize = usize::MAX; -#[cfg(feature = "unchecked")] -pub const MAX_ARRAY_SIZE: usize = usize::MAX; -#[cfg(feature = "unchecked")] -pub const MAX_MAP_SIZE: usize = usize::MAX; +pub const MAX_FUNCTION_EXPR_DEPTH: usize = 0; pub const KEYWORD_PRINT: &str = "print"; pub const KEYWORD_DEBUG: &str = "debug"; @@ -189,7 +182,7 @@ pub struct State { /// Number of operations performed. pub operations: u64, /// Number of modules loaded. - pub modules: u64, + pub modules: usize, } impl State { @@ -268,7 +261,7 @@ pub struct Engine { /// Maximum number of operations allowed to run. pub(crate) max_operations: u64, /// Maximum number of modules allowed to load. - pub(crate) max_modules: u64, + pub(crate) max_modules: usize, /// Maximum length of a string. pub(crate) max_string_size: usize, /// Maximum length of an array. @@ -309,11 +302,11 @@ impl Default for Engine { max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_expr_depth: MAX_EXPR_DEPTH, max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: u64::MAX, - max_modules: u64::MAX, - max_string_size: usize::MAX, - max_array_size: usize::MAX, - max_map_size: usize::MAX, + max_operations: 0, + max_modules: usize::MAX, + max_string_size: 0, + max_array_size: 0, + max_map_size: 0, }; engine.load_package(StandardPackage::new().get()); @@ -456,11 +449,11 @@ impl Engine { max_call_stack_depth: MAX_CALL_STACK_DEPTH, max_expr_depth: MAX_EXPR_DEPTH, max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH, - max_operations: u64::MAX, - max_modules: u64::MAX, - max_string_size: usize::MAX, - max_array_size: usize::MAX, - max_map_size: usize::MAX, + max_operations: 0, + max_modules: usize::MAX, + max_string_size: 0, + max_array_size: 0, + max_map_size: 0, } } @@ -501,17 +494,13 @@ impl Engine { /// consuming too much resources (0 for unlimited). #[cfg(not(feature = "unchecked"))] pub fn set_max_operations(&mut self, operations: u64) { - self.max_operations = if operations == 0 { - u64::MAX - } else { - operations - }; + self.max_operations = operations; } - /// Set the maximum number of imported modules allowed for a script (0 for unlimited). + /// Set the maximum number of imported modules allowed for a script. #[cfg(not(feature = "unchecked"))] - pub fn set_max_modules(&mut self, modules: u64) { - self.max_modules = if modules == 0 { u64::MAX } else { modules }; + pub fn set_max_modules(&mut self, modules: usize) { + self.max_modules = modules; } /// Set the depth limits for expressions/statements (0 for unlimited). @@ -656,7 +645,7 @@ impl Engine { return Ok((result, false)); } else { // Run external function - let result = func.get_native_fn()(args)?; + let result = func.get_native_fn()(self, args)?; // Restore the original reference restore_first_arg(old_this_ptr, args); @@ -1485,7 +1474,7 @@ impl Engine { .or_else(|| self.packages.get_fn(hash_fn)) { // Overriding exact implementation - func(&mut [lhs_ptr, &mut rhs_val])?; + func(self, &mut [lhs_ptr, &mut rhs_val])?; } else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() { // Not built in, map to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = @@ -1717,7 +1706,9 @@ impl Engine { .map_err(|err| EvalAltResult::new_position(err, *pos)) } Ok(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), *pos), - Ok(f) => f.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)), + Ok(f) => { + f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos)) + } Err(err) if def_val.is_some() && matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) => @@ -1773,11 +1764,7 @@ impl Engine { _ => unreachable!(), }; - if let Ok(val) = &result { - self.check_data_size(val)?; - } - - result + self.check_data_size(result) } /// Evaluate a statement @@ -2046,50 +2033,102 @@ impl Engine { } }; - if let Ok(val) = &result { - self.check_data_size(val)?; - } - - result + self.check_data_size(result) } - /// Check a `Dynamic` value to ensure that its size is within allowable limit. - fn check_data_size(&self, value: &Dynamic) -> Result<(), Box> { + /// Check a result to ensure that the data size is within allowable limit. + fn check_data_size( + &self, + result: Result>, + ) -> Result> { #[cfg(feature = "unchecked")] - return Ok(()); + return result; - match value { - Dynamic(Union::Str(s)) - if self.max_string_size > 0 && s.len() > self.max_string_size => - { - Err(Box::new(EvalAltResult::ErrorDataTooLarge( - "Length of string".to_string(), - self.max_string_size, - s.len(), - Position::none(), - ))) + // If no data size limits, just return + if self.max_string_size + self.max_array_size + self.max_map_size == 0 { + return result; + } + + // Recursively calculate the size of a value (especially `Array` and `Map`) + fn calc_size(value: &Dynamic) -> (usize, usize, usize) { + match value { + #[cfg(not(feature = "no_index"))] + Dynamic(Union::Array(arr)) => { + let mut arrays = 0; + let mut maps = 0; + + arr.iter().for_each(|value| match value { + Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => { + let (a, m, _) = calc_size(value); + arrays += a; + maps += m; + } + _ => arrays += 1, + }); + + (arrays, maps, 0) + } + #[cfg(not(feature = "no_object"))] + Dynamic(Union::Map(map)) => { + let mut arrays = 0; + let mut maps = 0; + + map.values().for_each(|value| match value { + Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => { + let (a, m, _) = calc_size(value); + arrays += a; + maps += m; + } + _ => maps += 1, + }); + + (arrays, maps, 0) + } + Dynamic(Union::Str(s)) => (0, 0, s.len()), + _ => (0, 0, 0), } + } + + 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"))] - Dynamic(Union::Array(arr)) - if self.max_array_size > 0 && arr.len() > self.max_array_size => - { - Err(Box::new(EvalAltResult::ErrorDataTooLarge( - "Length of array".to_string(), - self.max_array_size, - arr.len(), - Position::none(), - ))) - } + Ok(Dynamic(Union::Array(_))) if self.max_array_size > 0 => (), + // Map with limit #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(map)) if self.max_map_size > 0 && map.len() > self.max_map_size => { - Err(Box::new(EvalAltResult::ErrorDataTooLarge( - "Number of properties in object map".to_string(), - self.max_map_size, - map.len(), - Position::none(), - ))) - } - _ => Ok(()), + 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 { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + self.max_string_size, + s, + Position::none(), + ))) + } else if arr > self.max_array_size { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Size of array".to_string(), + self.max_array_size, + arr, + Position::none(), + ))) + } else if map > self.max_map_size { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Number of properties in object map".to_string(), + self.max_map_size, + map, + Position::none(), + ))) + } else { + result } } @@ -2101,7 +2140,7 @@ impl Engine { #[cfg(not(feature = "unchecked"))] { // Guard against too many operations - if state.operations > self.max_operations { + if self.max_operations > 0 && state.operations > self.max_operations { return Err(Box::new(EvalAltResult::ErrorTooManyOperations( Position::none(), ))); diff --git a/src/error.rs b/src/error.rs index 3d4c47c7..ce8615ed 100644 --- a/src/error.rs +++ b/src/error.rs @@ -22,8 +22,8 @@ pub enum LexError { MalformedChar(String), /// An identifier is in an invalid format. MalformedIdentifier(String), - /// Bad keyword encountered when tokenizing the script text. - ImproperKeyword(String), + /// Bad symbol encountered when tokenizing the script text. + ImproperSymbol(String), } impl Error for LexError {} @@ -42,11 +42,18 @@ impl fmt::Display for LexError { "Length of string literal exceeds the maximum limit ({})", max ), - Self::ImproperKeyword(s) => write!(f, "{}", s), + Self::ImproperSymbol(s) => write!(f, "{}", s), } } } +impl LexError { + /// Convert a `LexError` into a `ParseError`. + pub fn into_err(&self, pos: Position) -> ParseError { + ParseError(Box::new(self.into()), pos) + } +} + /// Type of error encountered when parsing a script. /// /// Some errors never appear when certain features are turned on. @@ -217,6 +224,17 @@ impl fmt::Display for ParseErrorType { } } +impl From<&LexError> for ParseErrorType { + fn from(err: &LexError) -> Self { + match err { + LexError::StringTooLong(max) => { + Self::LiteralTooLarge("Length of string literal".to_string(), *max) + } + _ => Self::BadInput(err.to_string()), + } + } +} + /// Error when parsing a script. #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub struct ParseError(pub Box, pub Position); diff --git a/src/fn_native.rs b/src/fn_native.rs index 3eb5beaa..fc6e7673 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,4 +1,5 @@ use crate::any::Dynamic; +use crate::engine::Engine; use crate::parser::ScriptFnDef; use crate::plugin::PluginFunction; use crate::result::EvalAltResult; @@ -52,9 +53,10 @@ pub fn shared_take(value: Shared) -> T { pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result>; +pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result>; #[cfg(feature = "sync")] -pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result> + Send + Sync; +pub type FnAny = + dyn Fn(&Engine, &mut FnCallArgs) -> Result> + Send + Sync; pub type IteratorFn = fn(Dynamic) -> Box>; diff --git a/src/fn_register.rs b/src/fn_register.rs index e40b4f9a..cf83ae14 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,5 +1,4 @@ //! Module which defines the function registration mechanism. - #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; @@ -206,7 +205,7 @@ macro_rules! make_func { // ^ function parameter generic type name (A, B, C etc.) // ^ dereferencing function - Box::new(move |args: &mut FnCallArgs| { + Box::new(move |_: &Engine, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! #[allow(unused_variables, unused_mut)] diff --git a/src/module.rs b/src/module.rs index 91fa3388..d1206b5d 100644 --- a/src/module.rs +++ b/src/module.rs @@ -305,6 +305,29 @@ impl Module { hash_fn } + /// Set a Rust function taking a reference to the scripting `Engine`, plus a list of + /// mutable `Dynamic` references into the module, returning a hash key. + /// A list of `TypeId`'s is taken as the argument types. + /// + /// Use this to register a built-in function which must reference settings on the scripting + /// `Engine` (e.g. to prevent growing an array beyond the allowed maximum size). + /// + /// If there is a similar existing Rust function, it is replaced. + pub(crate) fn set_fn_var_args( + &mut self, + name: impl Into, + args: &[TypeId], + func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn + SendSync + 'static, + ) -> u64 { + let f = move |engine: &Engine, args: &mut FnCallArgs| func(engine, args).map(Dynamic::from); + self.set_fn( + name, + Public, + args, + CallableFunction::from_method(Box::new(f)), + ) + } + /// Set a Rust function taking no parameters into the module, returning a hash key. /// /// If there is a similar existing Rust function, it is replaced. @@ -323,7 +346,7 @@ impl Module { name: impl Into, func: impl Fn() -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |_: &mut FnCallArgs| func().map(Dynamic::from); + let f = move |_: &Engine, _: &mut FnCallArgs| func().map(Dynamic::from); let args = []; self.set_fn( name, @@ -351,8 +374,9 @@ impl Module { name: impl Into, func: impl Fn(A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = - move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::()).map(Dynamic::from); + let f = move |_: &Engine, args: &mut FnCallArgs| { + func(mem::take(args[0]).cast::()).map(Dynamic::from) + }; let args = [TypeId::of::()]; self.set_fn( name, @@ -380,7 +404,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { func(args[0].downcast_mut::().unwrap()).map(Dynamic::from) }; let args = [TypeId::of::()]; @@ -434,7 +458,7 @@ impl Module { name: impl Into, func: impl Fn(A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); @@ -470,7 +494,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -561,7 +585,7 @@ impl Module { name: impl Into, func: impl Fn(A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -603,7 +627,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -640,7 +664,7 @@ impl Module { &mut self, func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let a = args[0].downcast_mut::().unwrap(); @@ -682,7 +706,7 @@ impl Module { name: impl Into, func: impl Fn(A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let a = mem::take(args[0]).cast::(); let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); @@ -731,7 +755,7 @@ impl Module { name: impl Into, func: impl Fn(&mut A, B, C, D) -> FuncReturn + SendSync + 'static, ) -> u64 { - let f = move |args: &mut FnCallArgs| { + let f = move |_: &Engine, args: &mut FnCallArgs| { let b = mem::take(args[1]).cast::(); let c = mem::take(args[2]).cast::(); let d = mem::take(args[3]).cast::(); @@ -1019,7 +1043,7 @@ pub trait ModuleResolver: SendSync { /// Resolve a module based on a path string. fn resolve( &self, - engine: &Engine, + _: &Engine, scope: Scope, path: &str, pos: Position, diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 19206e36..c8615865 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -2,9 +2,11 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; -use crate::engine::Array; +use crate::engine::{Array, Engine}; use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +use crate::result::EvalAltResult; +use crate::token::Position; use crate::stdlib::{any::TypeId, boxed::Box}; @@ -23,13 +25,28 @@ fn ins(list: &mut Array, position: INT, item: T) -> FuncRetu } Ok(()) } -fn pad(list: &mut Array, len: INT, item: T) -> FuncReturn<()> { - if len >= 0 { +fn pad(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> { + let len = *args[1].downcast_ref::().unwrap(); + + // Check if array will be over max size limit + if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Size of array".to_string(), + engine.max_array_size, + len as usize, + Position::none(), + ))) + } else if len >= 0 { + let item = args[2].downcast_ref::().unwrap().clone(); + let list = args[0].downcast_mut::().unwrap(); + while list.len() < len as usize { push(list, item.clone())?; } + Ok(()) + } else { + Ok(()) } - Ok(()) } macro_rules! reg_op { @@ -42,11 +59,21 @@ macro_rules! reg_tri { $( $lib.set_fn_3_mut($op, $func::<$par>); )* }; } +macro_rules! reg_pad { + ($lib:expr, $op:expr, $func:ident, $($par:ty),*) => { + $({ + $lib.set_fn_var_args($op, + &[TypeId::of::(), TypeId::of::(), TypeId::of::<$par>()], + $func::<$par> + ); + })* + }; +} #[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); - reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); + reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { @@ -69,14 +96,14 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[cfg(not(feature = "only_i64"))] { reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_tri!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); + reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128); reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } #[cfg(not(feature = "no_float"))] { reg_op!(lib, "push", push, f32, f64); - reg_tri!(lib, "pad", pad, f32, f64); + reg_pad!(lib, "pad", pad, f32, f64); reg_tri!(lib, "insert", ins, f32, f64); } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index e86f5fb1..6b5928c1 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,12 +1,17 @@ +use crate::any::Dynamic; use crate::def_package; +use crate::engine::Engine; use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; +use crate::result::EvalAltResult; +use crate::token::Position; use crate::utils::StaticVec; #[cfg(not(feature = "no_index"))] use crate::engine::Array; use crate::stdlib::{ + any::TypeId, fmt::Display, format, string::{String, ToString}, @@ -210,14 +215,40 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str Ok(()) }, ); - lib.set_fn_3_mut( + lib.set_fn_var_args( "pad", - |s: &mut ImmutableString, len: INT, ch: char| { - let copy = s.make_mut(); - for _ in 0..copy.chars().count() - len as usize { - copy.push(ch); + &[TypeId::of::(), TypeId::of::(), TypeId::of::()], + |engine: &Engine, args: &mut [&mut Dynamic]| { + let len = *args[1].downcast_ref::< INT>().unwrap(); + + // Check if string will be over max size limit + if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + engine.max_string_size, + len as usize, + Position::none(), + ))) + } else { + let ch = *args[2].downcast_ref::< char>().unwrap(); + let s = args[0].downcast_mut::().unwrap(); + + let copy = s.make_mut(); + for _ in 0..copy.chars().count() - len as usize { + copy.push(ch); + } + + if engine.max_string_size > 0 && copy.len() > engine.max_string_size { + Err(Box::new(EvalAltResult::ErrorDataTooLarge( + "Length of string".to_string(), + engine.max_string_size, + copy.len(), + Position::none(), + ))) + } else { + Ok(()) + } } - Ok(()) }, ); lib.set_fn_3_mut( diff --git a/src/parser.rs b/src/parser.rs index d17657e0..acbb59e5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -298,7 +298,9 @@ impl ParseSettings { } /// Make sure that the current level of expression nesting is within the maximum limit. pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> { - if self.level > limit { + if limit == 0 { + Ok(()) + } else if self.level > limit { Err(PERR::ExprTooDeep.into_err(self.pos)) } else { Ok(()) @@ -624,7 +626,7 @@ impl Expr { Self::Variable(_) => true, - expr => expr.is_constant(), + _ => self.is_constant(), } } @@ -765,7 +767,7 @@ fn parse_paren_expr( // ( xxx ) (Token::RightParen, _) => Ok(expr), // ( - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( Token::RightParen.into(), @@ -798,7 +800,7 @@ fn parse_call_expr( .into_err(settings.pos)) } // id - Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)), + Token::LexError(err) => return Err(err.into_err(settings.pos)), // id() Token::RightParen => { eat_token(input, Token::RightParen); @@ -878,9 +880,7 @@ fn parse_call_expr( .into_err(*pos)) } // id(...args - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(*pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // id(...args ??? (_, pos) => { return Err(PERR::MissingToken( @@ -1063,7 +1063,7 @@ fn parse_index_chain( } } } - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)), + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => Err(PERR::MissingToken( Token::RightBracket.into(), "for a matching [ in this index expression".into(), @@ -1108,9 +1108,7 @@ fn parse_array_literal( ) .into_err(*pos)) } - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(*pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => { return Err(PERR::MissingToken( Token::Comma.into(), @@ -1142,9 +1140,7 @@ fn parse_map_literal( let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::StringConst(s), pos) => (s, pos), - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) if map.is_empty() => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) @@ -1162,9 +1158,7 @@ fn parse_map_literal( match input.next().unwrap() { (Token::Colon, _) => (), - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken( Token::Colon.into(), @@ -1203,9 +1197,7 @@ fn parse_map_literal( ) .into_err(*pos)) } - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(*pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), (_, pos) => { return Err( PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into()) @@ -1267,8 +1259,8 @@ fn parse_primary( Token::MapStart => parse_map_literal(input, state, settings.level_up())?, Token::True => Expr::True(settings.pos), Token::False => Expr::False(settings.pos), - Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)), - token => { + Token::LexError(err) => return Err(err.into_err(settings.pos)), + _ => { return Err( PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos) ) @@ -1378,12 +1370,7 @@ fn parse_unary( None } }) - .ok_or_else(|| { - PERR::BadInput( - LexError::MalformedNumber(format!("-{}", x.0)).to_string(), - ) - .into_err(pos) - }) + .ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos)) } // Negative float @@ -1846,7 +1833,7 @@ fn parse_binary_op( make_dot_expr(current_lhs, rhs, pos)? } - token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)), + op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)), }; } } @@ -1988,7 +1975,7 @@ fn parse_for( // Variable name (Token::Identifier(s), _) => s, // Bad identifier - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), // EOF (Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)), // Not a variable name @@ -1998,7 +1985,7 @@ fn parse_for( // for name in ... match input.next().unwrap() { (Token::In, _) => (), - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err( PERR::MissingToken(Token::In.into(), "after the iteration variable".into()) @@ -2036,7 +2023,7 @@ fn parse_let( // let name ... let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -2107,7 +2094,7 @@ fn parse_import( // import expr as name ... let (name, _) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -2130,9 +2117,7 @@ fn parse_export( loop { let (id, id_pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s.clone(), pos), - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -2187,7 +2172,7 @@ fn parse_block( // Must start with { settings.pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken( Token::LeftBrace.into(), @@ -2227,9 +2212,7 @@ fn parse_block( // { ... { stmt } ??? (_, _) if !need_semicolon => (), // { ... stmt - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(*pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // { ... stmt ??? (_, pos) => { // Semicolons are not optional between statements @@ -2378,9 +2361,7 @@ fn parse_fn( state.push((s.clone(), ScopeEntryType::Normal)); params.push((s, pos)) } - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos)) } @@ -2392,9 +2373,7 @@ fn parse_fn( (Token::Identifier(_), pos) => { return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(pos)), (_, pos) => { return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos)) } @@ -2565,9 +2544,7 @@ impl Engine { // { stmt } ??? (_, _) if !need_semicolon => (), // stmt - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(*pos)) - } + (Token::LexError(err), pos) => return Err(err.into_err(*pos)), // stmt ??? (_, pos) => { // Semicolons are not optional between statements diff --git a/src/token.rs b/src/token.rs index 614783b4..0bb0cbe3 100644 --- a/src/token.rs +++ b/src/token.rs @@ -862,6 +862,14 @@ impl<'a> TokenIterator<'a> { self.eat_next(); return Some((Token::MinusAssign, pos)); } + ('-', '>') => { + return Some(( + Token::LexError(Box::new(LERR::ImproperSymbol( + "'->' is not a valid symbol. This is not C or C++!".to_string(), + ))), + pos, + )) + } ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), ('-', _) => return Some((Token::Minus, pos)), @@ -931,7 +939,7 @@ impl<'a> TokenIterator<'a> { // Warn against `===` if self.peek_next() == Some('=') { return Some(( - Token::LexError(Box::new(LERR::ImproperKeyword( + Token::LexError(Box::new(LERR::ImproperSymbol( "'===' is not a valid operator. This is not JavaScript! Should it be '=='?" .to_string(), ))), @@ -941,18 +949,44 @@ impl<'a> TokenIterator<'a> { return Some((Token::EqualsTo, pos)); } + ('=', '>') => { + return Some(( + Token::LexError(Box::new(LERR::ImproperSymbol( + "'=>' is not a valid symbol. This is not Rust! Should it be '>='?" + .to_string(), + ))), + pos, + )) + } ('=', _) => return Some((Token::Equals, pos)), (':', ':') => { self.eat_next(); return Some((Token::DoubleColon, pos)); } + (':', '=') => { + return Some(( + Token::LexError(Box::new(LERR::ImproperSymbol( + "':=' is not a valid assignment operator. This is not Pascal! Should it be simply '='?" + .to_string(), + ))), + pos, + )) + } (':', _) => return Some((Token::Colon, pos)), ('<', '=') => { self.eat_next(); return Some((Token::LessThanEqualsTo, pos)); } + ('<', '-') => { + return Some(( + Token::LexError(Box::new(LERR::ImproperSymbol( + "'<-' is not a valid symbol. Should it be '<='?".to_string(), + ))), + pos, + )) + } ('<', '<') => { self.eat_next(); @@ -993,7 +1027,7 @@ impl<'a> TokenIterator<'a> { // Warn against `!==` if self.peek_next() == Some('=') { return Some(( - Token::LexError(Box::new(LERR::ImproperKeyword( + Token::LexError(Box::new(LERR::ImproperSymbol( "'!==' is not a valid operator. This is not JavaScript! Should it be '!='?" .to_string(), ))), diff --git a/tests/data_size.rs b/tests/data_size.rs index 637fde5e..e073d443 100644 --- a/tests/data_size.rs +++ b/tests/data_size.rs @@ -14,7 +14,12 @@ fn test_max_string_size() -> Result<(), Box> { assert!(matches!( engine.compile(r#"let x = "hello, world!";"#).expect_err("should error"), - ParseError(x, _) if *x == ParseErrorType::BadInput("Length of string literal exceeds the maximum limit (10)".to_string()) + ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) + )); + + assert!(matches!( + engine.compile(r#"let x = "朝に紅顔、暮に白骨";"#).expect_err("should error"), + ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10) )); assert!(matches!( @@ -30,6 +35,19 @@ fn test_max_string_size() -> Result<(), Box> { EvalAltResult::ErrorDataTooLarge(_, 10, 13, _) )); + assert!(matches!( + *engine + .eval::( + r#" + let x = "hello"; + x.pad(100, '!'); + x + "# + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 100, _) + )); + engine.set_max_string_size(0); assert_eq!( @@ -38,7 +56,7 @@ fn test_max_string_size() -> Result<(), Box> { let x = "hello, "; let y = "world!"; x + y - "# + "# )?, "hello, world!" ); @@ -52,6 +70,9 @@ fn test_max_array_size() -> Result<(), Box> { let mut engine = Engine::new(); engine.set_max_array_size(10); + #[cfg(not(feature = "no_object"))] + engine.set_max_map_size(10); + assert!(matches!( engine .compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];") @@ -71,6 +92,57 @@ fn test_max_array_size() -> Result<(), Box> { .expect_err("should error"), EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) )); + assert!(matches!( + *engine + .eval::( + r" + let x = [1,2,3,4,5,6]; + x.pad(100, 42); + x + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 100, _) + )); + + assert!(matches!( + *engine + .eval::( + r" + let x = [1,2,3]; + [x, x, x, x] + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) + )); + + #[cfg(not(feature = "no_object"))] + assert!(matches!( + *engine + .eval::( + r" + let x = #{a:1, b:2, c:3}; + [x, x, x, x] + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) + )); + + assert!(matches!( + *engine + .eval::( + r" + let x = [1]; + let y = [x, x]; + let z = [y, y]; + [z, z, z] + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) + )); engine.set_max_array_size(0); @@ -81,12 +153,24 @@ fn test_max_array_size() -> Result<(), Box> { let x = [1,2,3,4,5,6]; let y = [7,8,9,10,11,12]; x + y - " + " )? .len(), 12 ); + assert_eq!( + engine + .eval::( + r" + let x = [1,2,3]; + [x, x, x, x] + " + )? + .len(), + 4 + ); + Ok(()) } @@ -96,6 +180,9 @@ fn test_max_map_size() -> Result<(), Box> { let mut engine = Engine::new(); engine.set_max_map_size(10); + #[cfg(not(feature = "no_index"))] + engine.set_max_array_size(10); + assert!(matches!( engine .compile("let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};") @@ -116,6 +203,31 @@ fn test_max_map_size() -> Result<(), Box> { EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) )); + assert!(matches!( + *engine + .eval::( + r" + let x = #{a:1,b:2,c:3}; + #{u:x, v:x, w:x, z:x} + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) + )); + + #[cfg(not(feature = "no_index"))] + assert!(matches!( + *engine + .eval::( + r" + let x = [1, 2, 3]; + #{u:x, v:x, w:x, z:x} + " + ) + .expect_err("should error"), + EvalAltResult::ErrorDataTooLarge(_, 10, 12, _) + )); + engine.set_max_map_size(0); assert_eq!( @@ -125,11 +237,23 @@ fn test_max_map_size() -> Result<(), Box> { let x = #{a:1,b:2,c:3,d:4,e:5,f:6}; let y = #{g:7,h:8,i:9,j:10,k:11,l:12}; x + y - " + " )? .len(), 12 ); + assert_eq!( + engine + .eval::( + r" + let x = #{a:1,b:2,c:3}; + #{u:x, v:x, w:x, z:x} + " + )? + .len(), + 4 + ); + Ok(()) } diff --git a/tests/modules.rs b/tests/modules.rs index 10c138ad..771aa490 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,7 +1,9 @@ #![cfg(not(feature = "no_module"))] use rhai::{ - module_resolvers, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope, INT, + module_resolvers, Dynamic, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope, + INT, }; +use std::any::TypeId; #[test] fn test_module() { @@ -20,6 +22,7 @@ fn test_module_sub_module() -> Result<(), Box> { let mut sub_module2 = Module::new(); sub_module2.set_var("answer", 41 as INT); + let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1)); sub_module.set_sub_module("universe", sub_module2); @@ -130,7 +133,7 @@ fn test_module_resolver() -> Result<(), Box> { EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo" )); - engine.set_max_modules(0); + engine.set_max_modules(1000); #[cfg(not(feature = "no_function"))] engine.eval::<()>( diff --git a/tests/operations.rs b/tests/operations.rs index 91df26c9..dae539f0 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -111,3 +111,20 @@ fn test_max_operations_eval() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_max_operations_progress() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.set_max_operations(500); + + engine.on_progress(|&count| count < 100); + + assert!(matches!( + *engine + .eval::<()>("for x in range(0, 500) {}") + .expect_err("should error"), + EvalAltResult::ErrorTerminated(_) + )); + + Ok(()) +}