Refine no_function feature.
This commit is contained in:
parent
467b109c23
commit
d626bf9f5b
@ -21,7 +21,7 @@ num-traits = { version = "0.2.11", default-features = false }
|
||||
|
||||
[features]
|
||||
#default = ["unchecked", "sync", "no_optimize", "no_float", "only_i32", "no_index", "no_object", "no_function", "no_module"]
|
||||
default = ["serde"]
|
||||
default = []
|
||||
plugins = []
|
||||
unchecked = [] # unchecked arithmetic
|
||||
sync = [] # restrict to only types that implement Send + Sync
|
||||
|
@ -29,7 +29,7 @@ Features
|
||||
* Fairly efficient evaluation (1 million iterations in 0.4 sec on a single core, 2.3 GHz Linux VM).
|
||||
* Relatively little `unsafe` code (yes there are some for performance reasons, and most `unsafe` code is limited to
|
||||
one single source file, all with names starting with `"unsafe_"`).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the [`sync`] feature).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||
* Sand-boxed - the scripting engine, if declared immutable, cannot mutate the containing environment unless explicitly permitted (e.g. via a `RefCell`).
|
||||
* Rugged - protected against malicious attacks (such as [stack-overflow](https://schungx.github.io/rhai/safety/max-call-stack.html), [over-sized data](https://schungx.github.io/rhai/safety/max-string-size.html), and [runaway scripts](https://schungx.github.io/rhai/safety/max-operations.html) etc.) that may come from untrusted third-party user-land scripts.
|
||||
* Track script evaluation [progress](https://schungx.github.io/rhai/safety/progress.html) and manually terminate a script run.
|
||||
@ -38,7 +38,7 @@ Features
|
||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html).
|
||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
||||
* Serialization/deserialization support via [serde](https://crates.io/crates/serde)
|
||||
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
|
||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
||||
|
||||
|
13
src/api.rs
13
src/api.rs
@ -1,10 +1,7 @@
|
||||
//! Module that defines the extern API of `Engine`.
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::engine::{
|
||||
get_script_function_by_signature, make_getter, make_setter, Engine, Imports, State, FN_IDX_GET,
|
||||
FN_IDX_SET,
|
||||
};
|
||||
use crate::engine::{make_getter, make_setter, Engine, Imports, State, FN_IDX_GET, FN_IDX_SET};
|
||||
use crate::error::ParseError;
|
||||
use crate::fn_call::FuncArgs;
|
||||
use crate::fn_native::{IteratorFn, SendSync};
|
||||
@ -19,6 +16,9 @@ use crate::utils::StaticVec;
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
use crate::engine::Map;
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
use crate::engine::get_script_function_by_signature;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::{type_name, TypeId},
|
||||
boxed::Box,
|
||||
@ -1189,6 +1189,7 @@ impl Engine {
|
||||
/// This is to avoid unnecessarily cloning the arguments.
|
||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||
/// clone them _before_ calling this function.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn call_fn_dynamic_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -1240,6 +1241,7 @@ impl Engine {
|
||||
mut ast: AST,
|
||||
optimization_level: OptimizationLevel,
|
||||
) -> AST {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let lib = ast
|
||||
.lib()
|
||||
.iter_fn()
|
||||
@ -1247,6 +1249,9 @@ impl Engine {
|
||||
.map(|(_, _, _, f)| f.get_fn_def().clone())
|
||||
.collect();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
let lib = Default::default();
|
||||
|
||||
let stmt = mem::take(ast.statements_mut());
|
||||
optimize_into_ast(self, scope, stmt, lib, optimization_level)
|
||||
}
|
||||
|
@ -216,6 +216,7 @@ impl State {
|
||||
}
|
||||
|
||||
/// Get a script-defined function definition from a module.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_script_function_by_signature<'a>(
|
||||
module: &'a Module,
|
||||
name: &str,
|
||||
@ -767,22 +768,23 @@ impl Engine {
|
||||
.or_else(|| self.packages.get_fn(hash_fn));
|
||||
|
||||
if let Some(func) = func {
|
||||
// Calling pure function but the first argument is a reference?
|
||||
normalize_first_arg(
|
||||
is_ref && (func.is_pure() || (func.is_script() && !is_method)),
|
||||
&mut this_copy,
|
||||
&mut old_this_ptr,
|
||||
args,
|
||||
);
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let need_normalize = is_ref && (func.is_pure() || (func.is_script() && !is_method));
|
||||
#[cfg(feature = "no_function")]
|
||||
let need_normalize = is_ref && func.is_pure();
|
||||
|
||||
// Calling pure function but the first argument is a reference?
|
||||
normalize_first_arg(need_normalize, &mut this_copy, &mut old_this_ptr, args);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if func.is_script() {
|
||||
// Run scripted function
|
||||
let fn_def = func.get_fn_def();
|
||||
|
||||
// Method call of script function - map first argument to `this`
|
||||
if is_method {
|
||||
return if is_method {
|
||||
let (first, rest) = args.split_at_mut(1);
|
||||
return Ok((
|
||||
Ok((
|
||||
self.call_script_fn(
|
||||
scope,
|
||||
mods,
|
||||
@ -795,7 +797,7 @@ impl Engine {
|
||||
level,
|
||||
)?,
|
||||
false,
|
||||
));
|
||||
))
|
||||
} else {
|
||||
let result = self.call_script_fn(
|
||||
scope, mods, state, lib, &mut None, fn_name, fn_def, args, level,
|
||||
@ -804,9 +806,10 @@ impl Engine {
|
||||
// Restore the original reference
|
||||
restore_first_arg(old_this_ptr, args);
|
||||
|
||||
return Ok((result, false));
|
||||
Ok((result, false))
|
||||
};
|
||||
} else {
|
||||
}
|
||||
|
||||
// Run external function
|
||||
let result = func.get_native_fn()(self, args)?;
|
||||
|
||||
@ -840,7 +843,6 @@ impl Engine {
|
||||
_ => (result, func.is_method()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// See if it is built in.
|
||||
if args.len() == 2 {
|
||||
@ -2016,6 +2018,7 @@ impl Engine {
|
||||
};
|
||||
|
||||
match func {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Ok(f) if f.is_script() => {
|
||||
let args = args.as_mut();
|
||||
let fn_def = f.get_fn_def();
|
||||
|
@ -114,6 +114,7 @@ pub enum CallableFunction {
|
||||
/// An iterator function.
|
||||
Iterator(IteratorFn),
|
||||
/// A script-defined function.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Script(Shared<ScriptFnDef>),
|
||||
}
|
||||
|
||||
@ -123,6 +124,8 @@ impl fmt::Debug for CallableFunction {
|
||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||
Self::Method(_) => write!(f, "NativeMethod"),
|
||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
|
||||
}
|
||||
}
|
||||
@ -134,6 +137,8 @@ impl fmt::Display for CallableFunction {
|
||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||
Self::Method(_) => write!(f, "NativeMethod"),
|
||||
Self::Iterator(_) => write!(f, "NativeIterator"),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
|
||||
}
|
||||
}
|
||||
@ -144,24 +149,34 @@ impl CallableFunction {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Pure(_) => true,
|
||||
Self::Method(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||
Self::Method(_) | Self::Iterator(_) => false,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this a native Rust method function?
|
||||
pub fn is_method(&self) -> bool {
|
||||
match self {
|
||||
Self::Method(_) => true,
|
||||
Self::Pure(_) | Self::Iterator(_) | Self::Script(_) => false,
|
||||
Self::Pure(_) | Self::Iterator(_) => false,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this an iterator function?
|
||||
pub fn is_iter(&self) -> bool {
|
||||
match self {
|
||||
Self::Iterator(_) => true,
|
||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => false,
|
||||
Self::Pure(_) | Self::Method(_) => false,
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(_) => false,
|
||||
}
|
||||
}
|
||||
/// Is this a Rhai-scripted function?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn is_script(&self) -> bool {
|
||||
match self {
|
||||
Self::Script(_) => true,
|
||||
@ -176,7 +191,10 @@ impl CallableFunction {
|
||||
pub fn get_native_fn(&self) -> &FnAny {
|
||||
match self {
|
||||
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
||||
Self::Iterator(_) | Self::Script(_) => unreachable!(),
|
||||
Self::Iterator(_) => unreachable!(),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
/// Get a shared reference to a script-defined function definition.
|
||||
@ -184,6 +202,7 @@ impl CallableFunction {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_shared_fn_def(&self) -> Shared<ScriptFnDef> {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
|
||||
@ -195,6 +214,7 @@ impl CallableFunction {
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the `CallableFunction` is not `Script`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
||||
match self {
|
||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => unreachable!(),
|
||||
@ -209,7 +229,10 @@ impl CallableFunction {
|
||||
pub fn get_iter_fn(&self) -> IteratorFn {
|
||||
match self {
|
||||
Self::Iterator(f) => *f,
|
||||
Self::Pure(_) | Self::Method(_) | Self::Script(_) => unreachable!(),
|
||||
Self::Pure(_) | Self::Method(_) => unreachable!(),
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Self::Script(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
/// Create a new `CallableFunction::Pure`.
|
||||
@ -228,12 +251,14 @@ impl From<IteratorFn> for CallableFunction {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl From<ScriptFnDef> for CallableFunction {
|
||||
fn from(func: ScriptFnDef) -> Self {
|
||||
Self::Script(func.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl From<Shared<ScriptFnDef>> for CallableFunction {
|
||||
fn from(func: Shared<ScriptFnDef>) -> Self {
|
||||
Self::Script(func)
|
||||
|
@ -125,17 +125,23 @@ pub use parser::FLOAT;
|
||||
pub use module::ModuleResolver;
|
||||
|
||||
/// Module containing all built-in _module resolvers_ available to Rhai.
|
||||
///
|
||||
/// Not available under the `no_module` feature.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mod module_resolvers {
|
||||
pub use crate::module::resolvers::*;
|
||||
}
|
||||
|
||||
/// Serialization support for [`serde`](https://crates.io/crates/serde).
|
||||
///
|
||||
/// Requires the `serde` feature.
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod ser {
|
||||
pub use crate::serde::ser::to_dynamic;
|
||||
}
|
||||
/// Deserialization support for [`serde`](https://crates.io/crates/serde).
|
||||
///
|
||||
/// Requires the `serde` feature.
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod de {
|
||||
pub use crate::serde::de::from_dynamic;
|
||||
|
@ -236,6 +236,7 @@ impl Module {
|
||||
/// Set a script-defined function into the module.
|
||||
///
|
||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn set_script_fn(&mut self, fn_def: ScriptFnDef) {
|
||||
// None + function name + number of arguments.
|
||||
let hash_script = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
||||
@ -876,6 +877,7 @@ impl Module {
|
||||
.functions
|
||||
.iter()
|
||||
.filter(|(_, (_, _, _, v))| match v {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
CallableFunction::Script(ref f) => {
|
||||
filter(f.access, f.name.as_str(), f.params.len())
|
||||
}
|
||||
@ -893,6 +895,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Filter out the functions, retaining only some based on a filter predicate.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub(crate) fn retain_functions(&mut self, filter: impl Fn(FnAccess, &str, usize) -> bool) {
|
||||
self.functions.retain(|_, (_, _, _, v)| match v {
|
||||
CallableFunction::Script(ref f) => filter(f.access, f.name.as_str(), f.params.len()),
|
||||
@ -930,6 +933,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Get an iterator over all script-defined functions in the module.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub fn iter_script_fn<'a>(&'a self) -> impl Iterator<Item = Shared<ScriptFnDef>> + 'a {
|
||||
self.functions
|
||||
.values()
|
||||
@ -1014,6 +1018,7 @@ impl Module {
|
||||
Public => (),
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if func.is_script() {
|
||||
let fn_def = func.get_shared_fn_def();
|
||||
// Qualifiers + function name + number of arguments.
|
||||
@ -1024,7 +1029,9 @@ impl Module {
|
||||
empty(),
|
||||
);
|
||||
functions.push((hash_qualified_script, fn_def.into()));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Qualified Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
@ -1039,7 +1046,6 @@ impl Module {
|
||||
functions.push((hash_qualified_fn, func.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.indexed {
|
||||
return;
|
||||
|
@ -551,11 +551,17 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
|
||||
|
||||
// First search in functions lib (can override built-in)
|
||||
// Cater for both normal function call style and method call style (one additional arguments)
|
||||
if state.lib.iter_fn().find(|(_, _, _, f)| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
let has_script_fn = state.lib.iter_fn().find(|(_, _, _, f)| {
|
||||
if !f.is_script() { return false; }
|
||||
let fn_def = f.get_fn_def();
|
||||
&fn_def.name == name && (args.len()..=args.len() + 1).contains(&fn_def.params.len())
|
||||
}).is_some() {
|
||||
}).is_some();
|
||||
|
||||
#[cfg(feature = "no_function")]
|
||||
const has_script_fn: bool = false;
|
||||
|
||||
if has_script_fn {
|
||||
// A script-defined function overrides the built-in function - do not make the call
|
||||
x.3 = x.3.into_iter().map(|a| optimize_expr(a, state)).collect();
|
||||
return Expr::FnCall(x);
|
||||
|
Loading…
Reference in New Issue
Block a user