Reduce unnecessary data size checking.

This commit is contained in:
Stephen Chung 2022-01-06 22:10:16 +08:00
parent de6cb36503
commit c75d51ae88
4 changed files with 105 additions and 102 deletions

View File

@ -361,6 +361,7 @@ impl<'a> Target<'a> {
} }
} }
/// Get the source [`Dynamic`] of the [`Target`]. /// Get the source [`Dynamic`] of the [`Target`].
#[allow(dead_code)]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn source(&self) -> &Dynamic { pub fn source(&self) -> &Dynamic {
@ -1459,9 +1460,6 @@ impl Engine {
} }
} }
#[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?;
Ok(result) Ok(result)
} }
// xxx[rhs] op= new_val // xxx[rhs] op= new_val
@ -1478,6 +1476,8 @@ impl Engine {
global, state, lib, op_info, op_pos, obj_ptr, root, new_val, global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
) )
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(obj_ptr, new_pos)?;
None None
} }
// Can't index - try to call an index setter // Can't index - try to call an index setter
@ -1501,9 +1501,6 @@ impl Engine {
)?; )?;
} }
#[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?;
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// xxx[rhs] // xxx[rhs]
@ -1549,7 +1546,7 @@ impl Engine {
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?; self.check_data_size(target.source(), new_pos)?;
Ok((Dynamic::UNIT, true)) Ok((Dynamic::UNIT, true))
} }
// {xxx:map}.id // {xxx:map}.id
@ -1605,9 +1602,6 @@ impl Engine {
) )
.map_err(|err| err.fill_position(new_pos))?; .map_err(|err| err.fill_position(new_pos))?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?;
new_val = orig_val; new_val = orig_val;
} }
@ -1799,9 +1793,6 @@ impl Engine {
_ => Err(err), _ => Err(err),
}, },
)?; )?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?;
} }
Ok((result, may_be_changed)) Ok((result, may_be_changed))
@ -1867,7 +1858,7 @@ impl Engine {
let is_assignment = new_val.is_some(); let is_assignment = new_val.is_some();
let result = match lhs { match lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, var_pos, x) => { Expr::Variable(_, var_pos, x) => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -1900,12 +1891,6 @@ impl Engine {
.map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v }) .map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v })
.map_err(|err| err.fill_position(op_pos)) .map_err(|err| err.fill_position(op_pos))
} }
};
if is_assignment {
result.map(|_| Dynamic::UNIT)
} else {
self.check_return_value(result, expr.position())
} }
} }
@ -2355,7 +2340,7 @@ impl Engine {
.. ..
} = expr; } = expr;
let result = if let Some(namespace) = namespace.as_ref() { if let Some(namespace) = namespace.as_ref() {
// Qualified function call // Qualified function call
let hash = hashes.native; let hash = hashes.native;
@ -2369,9 +2354,7 @@ impl Engine {
scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture, scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture,
level, level,
) )
}; }
self.check_return_value(result, pos)
} }
/// Evaluate an expression. /// Evaluate an expression.
@ -2695,49 +2678,45 @@ impl Engine {
op, op,
}) = op_info }) = op_info
{ {
{ let mut lock_guard;
let mut lock_guard; let lhs_ptr_inner;
let lhs_ptr_inner;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared(); let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")] #[cfg(feature = "no_closure")]
let target_is_shared = false; let target_is_shared = false;
if target_is_shared { if target_is_shared {
lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`"); lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`");
lhs_ptr_inner = &mut *lock_guard; lhs_ptr_inner = &mut *lock_guard;
} else { } else {
lhs_ptr_inner = &mut *target; lhs_ptr_inner = &mut *target;
} }
let hash = hash_op_assign; let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val]; let args = &mut [lhs_ptr_inner, &mut new_val];
match self.call_native_fn(global, state, lib, op, hash, args, true, true, op_pos) { match self.call_native_fn(global, state, lib, op, hash, args, true, true, op_pos) {
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) => Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without = let op = &op[..op.len() - 1]; // extract operator without =
// Run function // Run function
let (value, _) = self.call_native_fn( let (value, _) = self.call_native_fn(
global, state, lib, op, hash_op, args, true, false, op_pos, global, state, lib, op, hash_op, args, true, false, op_pos,
)?; )?;
*args[0] = value.flatten(); *args[0] = value.flatten();
}
err => return err.map(|_| ()),
} }
err => return err.map(|_| ()),
} }
} else { } else {
// Normal assignment // Normal assignment
*target.as_mut() = new_val; *target.as_mut() = new_val;
} }
target.propagate_changed_value()?; target.propagate_changed_value()
self.check_data_size(target.source(), Position::NONE)
} }
/// Evaluate a statement. /// Evaluate a statement.
@ -2768,20 +2747,14 @@ impl Engine {
return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level); return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
} }
#[cfg(not(feature = "unchecked"))] // Then assignments.
self.inc_operations(&mut global.num_operations, stmt.position())?; // We shouldn't do this for too many variants because, soon or later, the added comparisons
// will cost more than the mis-predicted `match` branch.
if let Stmt::Assignment(x, op_pos) = stmt {
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?;
match stmt { return if x.0.is_variable_access(false) {
// No-op
Stmt::Noop(_) => Ok(Dynamic::UNIT),
// Expression as statement
Stmt::Expr(expr) => Ok(self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
.flatten()),
// var op= rhs
Stmt::Assignment(x, op_pos) if x.0.is_variable_access(false) => {
let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let (lhs_expr, op_info, rhs_expr) = x.as_ref();
let rhs_val = self let rhs_val = self
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)?
@ -2810,16 +2783,8 @@ impl Engine {
) )
.map_err(|err| err.fill_position(rhs_expr.position()))?; .map_err(|err| err.fill_position(rhs_expr.position()))?;
#[cfg(not(feature = "unchecked"))]
if op_info.is_some() {
self.check_data_size(lhs_ptr.as_ref(), lhs_expr.position())?;
}
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} } else {
// lhs op= rhs
Stmt::Assignment(x, op_pos) => {
let (lhs_expr, op_info, rhs_expr) = x.as_ref(); let (lhs_expr, op_info, rhs_expr) = x.as_ref();
let rhs_val = self let rhs_val = self
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)? .eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)?
@ -2850,7 +2815,20 @@ impl Engine {
} }
_ => unreachable!("cannot assign to expression: {:?}", lhs_expr), _ => unreachable!("cannot assign to expression: {:?}", lhs_expr),
} }
} };
}
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?;
match stmt {
// No-op
Stmt::Noop(_) => Ok(Dynamic::UNIT),
// Expression as statement
Stmt::Expr(expr) => Ok(self
.eval_expr(scope, global, state, lib, this_ptr, expr, level)?
.flatten()),
// Block scope // Block scope
Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT), Stmt::Block(statements, _) if statements.is_empty() => Ok(Dynamic::UNIT),
@ -3395,7 +3373,7 @@ impl Engine {
} }
/// Check a result to ensure that the data size is within allowable limit. /// Check a result to ensure that the data size is within allowable limit.
fn check_return_value(&self, mut result: RhaiResult, pos: Position) -> RhaiResult { pub(crate) fn check_return_value(&self, mut result: RhaiResult, pos: Position) -> RhaiResult {
let _pos = pos; let _pos = pos;
match result { match result {
@ -3470,7 +3448,11 @@ impl Engine {
} }
Union::Str(ref s, _, _) => (0, 0, s.len()), Union::Str(ref s, _, _) => (0, 0, s.len()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_, _, _) if !top => { Union::Shared(_, _, _) if top => {
Self::calc_data_sizes(&*value.read_lock::<Dynamic>().unwrap(), true)
}
#[cfg(not(feature = "no_closure"))]
Union::Shared(_, _, _) => {
unreachable!("shared values discovered within data: {}", value) unreachable!("shared values discovered within data: {}", value)
} }
_ => (0, 0, 0), _ => (0, 0, 0),
@ -3536,7 +3518,7 @@ impl Engine {
/// Check whether the size of a [`Dynamic`] is within limits. /// Check whether the size of a [`Dynamic`] is within limits.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> { pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> {
// If no data size limits, just return // If no data size limits, just return
if !self.has_data_size_limit() { if !self.has_data_size_limit() {
return Ok(()); return Ok(());

View File

@ -388,7 +388,14 @@ impl Engine {
bk.restore_first_arg(args) bk.restore_first_arg(args)
} }
let result = result.map_err(|err| err.fill_position(pos))?; // Check the return value (including data sizes)
let result = self.check_return_value(result, pos)?;
// Check the data size of any `&mut` object, which may be changed.
#[cfg(not(feature = "unchecked"))]
if is_ref_mut && args.len() > 0 {
self.check_data_size(&args[0], pos)?;
}
// See if the function match print/debug (which requires special processing) // See if the function match print/debug (which requires special processing)
return Ok(match name { return Ok(match name {
@ -1285,17 +1292,19 @@ impl Engine {
Some(f) if f.is_plugin_fn() => { Some(f) if f.is_plugin_fn() => {
let context = (self, fn_name, module.id(), &*global, lib, pos).into(); let context = (self, fn_name, module.id(), &*global, lib, pos).into();
f.get_plugin_fn() let result = f
.get_plugin_fn()
.expect("plugin function") .expect("plugin function")
.clone() .clone()
.call(context, &mut args) .call(context, &mut args);
.map_err(|err| err.fill_position(pos)) self.check_return_value(result, pos)
} }
Some(f) if f.is_native() => { Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function"); let func = f.get_native_fn().expect("native function");
let context = (self, fn_name, module.id(), &*global, lib, pos).into(); let context = (self, fn_name, module.id(), &*global, lib, pos).into();
func(context, &mut args).map_err(|err| err.fill_position(pos)) let result = func(context, &mut args);
self.check_return_value(result, pos)
} }
Some(f) => unreachable!("unknown function type: {:?}", f), Some(f) => unreachable!("unknown function type: {:?}", f),

View File

@ -3,7 +3,6 @@
use crate::engine::OP_EQUALS; use crate::engine::OP_EQUALS;
use crate::plugin::*; use crate::plugin::*;
use crate::types::dynamic::Union;
use crate::{ use crate::{
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext, def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
Position, RhaiResultOf, ERR, INT, Position, RhaiResultOf, ERR, INT,
@ -97,8 +96,9 @@ pub mod array_functions {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let check_sizes = match item.0 { let check_sizes = match item.0 {
Union::Array(_, _, _) | Union::Str(_, _, _) => true, crate::types::dynamic::Union::Array(_, _, _)
Union::Map(_, _, _) => true, | crate::types::dynamic::Union::Str(_, _, _) => true,
crate::types::dynamic::Union::Map(_, _, _) => true,
_ => false, _ => false,
}; };
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]

View File

@ -30,7 +30,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<String>( .run(
r#" r#"
let x = "hello, "; let x = "hello, ";
let y = "world!"; let y = "world!";
@ -44,7 +44,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<String>( .run(
r#" r#"
let x = "hello"; let x = "hello";
x.pad(100, '!'); x.pad(100, '!');
@ -90,7 +90,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .run(
" "
let x = [1,2,3,4,5,6]; let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12]; let y = [7,8,9,10,11,12];
@ -101,10 +101,22 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorDataTooLarge(_, _) EvalAltResult::ErrorDataTooLarge(_, _)
)); ));
assert!(matches!(
*engine
.run(
"
let x = [ 42 ];
loop { x[0] = x; }
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, _)
));
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .run(
" "
let x = [1,2,3,4,5,6]; let x = [1,2,3,4,5,6];
x.pad(100, 42); x.pad(100, 42);
@ -117,7 +129,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .run(
" "
let x = [1,2,3]; let x = [1,2,3];
[x, x, x, x] [x, x, x, x]
@ -130,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .run(
" "
let x = #{a:1, b:2, c:3}; let x = #{a:1, b:2, c:3};
[x, x, x, x] [x, x, x, x]
@ -142,7 +154,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Array>( .run(
" "
let x = [1]; let x = [1];
let y = [x, x]; let y = [x, x];
@ -220,7 +232,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Map>( .run(
" "
let x = #{a:1,b:2,c:3,d:4,e:5,f:6}; 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}; let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
@ -233,7 +245,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Map>( .run(
" "
let x = #{a:1,b:2,c:3}; let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x} #{u:x, v:x, w:x, z:x}
@ -246,7 +258,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine *engine
.eval::<Map>( .run(
" "
let x = [1, 2, 3]; let x = [1, 2, 3];
#{u:x, v:x, w:x, z:x} #{u:x, v:x, w:x, z:x}