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`].
#[allow(dead_code)]
#[inline]
#[must_use]
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)
}
// xxx[rhs] op= new_val
@ -1478,6 +1476,8 @@ impl Engine {
global, state, lib, op_info, op_pos, obj_ptr, root, new_val,
)
.map_err(|err| err.fill_position(new_pos))?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(obj_ptr, new_pos)?;
None
}
// 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))
}
// xxx[rhs]
@ -1549,7 +1546,7 @@ impl Engine {
.map_err(|err| err.fill_position(new_pos))?;
}
#[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))
}
// {xxx:map}.id
@ -1605,9 +1602,6 @@ impl Engine {
)
.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;
}
@ -1799,9 +1793,6 @@ impl Engine {
_ => Err(err),
},
)?;
#[cfg(not(feature = "unchecked"))]
self.check_data_size(target.as_ref(), root.1)?;
}
Ok((result, may_be_changed))
@ -1867,7 +1858,7 @@ impl Engine {
let is_assignment = new_val.is_some();
let result = match lhs {
match lhs {
// id.??? or id[???]
Expr::Variable(_, var_pos, x) => {
#[cfg(not(feature = "unchecked"))]
@ -1900,12 +1891,6 @@ impl Engine {
.map(|(v, _)| if is_assignment { Dynamic::UNIT } else { v })
.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;
let result = if let Some(namespace) = namespace.as_ref() {
if let Some(namespace) = namespace.as_ref() {
// Qualified function call
let hash = hashes.native;
@ -2369,9 +2354,7 @@ impl Engine {
scope, global, state, lib, this_ptr, name, args, constants, *hashes, pos, *capture,
level,
)
};
self.check_return_value(result, pos)
}
}
/// Evaluate an expression.
@ -2695,49 +2678,45 @@ impl Engine {
op,
}) = op_info
{
{
let mut lock_guard;
let lhs_ptr_inner;
let mut lock_guard;
let lhs_ptr_inner;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")]
let target_is_shared = false;
#[cfg(not(feature = "no_closure"))]
let target_is_shared = target.is_shared();
#[cfg(feature = "no_closure")]
let target_is_shared = false;
if target_is_shared {
lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`");
lhs_ptr_inner = &mut *lock_guard;
} else {
lhs_ptr_inner = &mut *target;
}
let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val];
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)) =>
{
// Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
// Run function
let (value, _) = self.call_native_fn(
global, state, lib, op, hash_op, args, true, false, op_pos,
)?;
*args[0] = value.flatten();
}
err => return err.map(|_| ()),
if target_is_shared {
lock_guard = target.write_lock::<Dynamic>().expect("`Dynamic`");
lhs_ptr_inner = &mut *lock_guard;
} else {
lhs_ptr_inner = &mut *target;
}
let hash = hash_op_assign;
let args = &mut [lhs_ptr_inner, &mut new_val];
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)) =>
{
// Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
// Run function
let (value, _) = self.call_native_fn(
global, state, lib, op, hash_op, args, true, false, op_pos,
)?;
*args[0] = value.flatten();
}
err => return err.map(|_| ()),
}
} else {
// Normal assignment
*target.as_mut() = new_val;
}
target.propagate_changed_value()?;
self.check_data_size(target.source(), Position::NONE)
target.propagate_changed_value()
}
/// Evaluate a statement.
@ -2768,20 +2747,14 @@ impl Engine {
return self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, *pos, level);
}
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut global.num_operations, stmt.position())?;
// Then assignments.
// 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 {
// 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) => {
return if x.0.is_variable_access(false) {
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
let rhs_val = self
.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()))?;
#[cfg(not(feature = "unchecked"))]
if op_info.is_some() {
self.check_data_size(lhs_ptr.as_ref(), lhs_expr.position())?;
}
Ok(Dynamic::UNIT)
}
// lhs op= rhs
Stmt::Assignment(x, op_pos) => {
} else {
let (lhs_expr, op_info, rhs_expr) = x.as_ref();
let rhs_val = self
.eval_expr(scope, global, state, lib, this_ptr, rhs_expr, level)?
@ -2850,7 +2815,20 @@ impl Engine {
}
_ => 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
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.
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;
match result {
@ -3470,7 +3448,11 @@ impl Engine {
}
Union::Str(ref s, _, _) => (0, 0, s.len()),
#[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)
}
_ => (0, 0, 0),
@ -3536,7 +3518,7 @@ impl Engine {
/// Check whether the size of a [`Dynamic`] is within limits.
#[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 !self.has_data_size_limit() {
return Ok(());

View File

@ -388,7 +388,14 @@ impl Engine {
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)
return Ok(match name {
@ -1285,17 +1292,19 @@ impl Engine {
Some(f) if f.is_plugin_fn() => {
let context = (self, fn_name, module.id(), &*global, lib, pos).into();
f.get_plugin_fn()
let result = f
.get_plugin_fn()
.expect("plugin function")
.clone()
.call(context, &mut args)
.map_err(|err| err.fill_position(pos))
.call(context, &mut args);
self.check_return_value(result, pos)
}
Some(f) if f.is_native() => {
let func = f.get_native_fn().expect("native function");
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),

View File

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

View File

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