Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix stack overflow #888

Merged
merged 3 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ Bug fixes
* Fixed stack overflow when calling closures recursively (thanks [`@MageWeiG`](https://github.com/MageWeiG) [880](https://github.com/rhaiscript/rhai/issues/880)).
* `Engine::call_fn` and `Engine::call_fn_with_options` now correctly use the `AST`'s `source` field.
* (Fuzzing) Fixed crash when using `..=` in strings.
* A recursive stack-overflow bug in `Dynamic::is_hashable` is fixed.

New features
------------

* The `break`, `continue`, `return` and `throw` statements can now follow the `??` operator to short-circuit operations where the value is `()`.
* A new symbol, `$func$`, is added to custom syntax to allow parsing of anonymous functions.
* The `filter`, `drain` and `retain` methods are added to object maps.


Version 1.18.0
Expand Down
3 changes: 1 addition & 2 deletions src/api/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,10 @@ impl Engine {
const X: bool,
R: Variant + Clone,
const F: bool,
FUNC: RhaiNativeFunc<A, N, X, R, F> + SendSync + 'static,
>(
&mut self,
name: impl AsRef<str> + Into<Identifier>,
func: FUNC,
func: impl RhaiNativeFunc<A, N, X, R, F> + SendSync + 'static,
) -> &mut Self {
FuncRegistration::new(name.into()).register_into_engine(self, func);

Expand Down
160 changes: 159 additions & 1 deletion src/packages/map_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use crate::engine::OP_EQUALS;
use crate::plugin::*;
use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT};
use crate::{
def_package, Dynamic, FnPtr, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;

Expand Down Expand Up @@ -293,6 +295,162 @@ mod map_functions {

map.values().cloned().collect()
}
/// Iterate through all the elements in the object map, applying a `filter` function to each
/// and return a new collection of all elements that return `true` as a new object map.
///
/// # Function Parameters
///
/// * `key`: current key
/// * `value` _(optional)_: copy of element (bound to `this` if omitted)
///
/// # Example
///
/// ```rhai
/// let x = #{a:1, b:2, c:3, d:4, e:5};
///
/// let y = x.filter(|k| this >= 3);
///
/// print(y); // prints #{"c":3, "d":4, "e":5}
///
/// let y = x.filter(|k, v| k != "d" && v < 5);
///
/// print(y); // prints #{"a":1, "b":2, "c":3}
/// ```
#[rhai_fn(return_raw)]
pub fn filter(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf<Map> {
if map.is_empty() {
return Ok(Map::new());
}

let mut result = Map::new();

for (key, item) in map.iter_mut() {
if filter
.call_raw_with_extra_args("filter", &ctx, Some(item), [key.into()], [], Some(1))?
.as_bool()
.unwrap_or(false)
{
result.insert(key.clone(), item.clone());
}
}

Ok(result)
}
/// Remove all elements in the object map that return `true` when applied the `filter` function and
/// return them as a new object map.
///
/// # Function Parameters
///
/// * `key`: current key
/// * `value` _(optional)_: copy of element (bound to `this` if omitted)
///
/// # Example
///
/// ```rhai
/// let x = #{a:1, b:2, c:3, d:4, e:5};
///
/// let y = x.drain(|k| this < 3);
///
/// print(x); // prints #{"c":3, "d":4, "e":5]
///
/// print(y); // prints #{"a":1, "b"2}
///
/// let z = x.drain(|k, v| k == "c" || v >= 5);
///
/// print(x); // prints #{"d":4}
///
/// print(z); // prints #{"c":3, "e":5}
/// ```
#[rhai_fn(return_raw)]
pub fn drain(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf<Map> {
if map.is_empty() {
return Ok(Map::new());
}

let mut drained = Map::new();
let mut retained = Map::new();

for (key, mut value) in mem::take(map).into_iter() {
if filter
.call_raw_with_extra_args(
"drain",
&ctx,
Some(&mut value),
[key.clone().into()],
[],
Some(1),
)?
.as_bool()
.unwrap_or(false)
{
drained.insert(key, value);
} else {
retained.insert(key, value);
}
}

*map = retained;

Ok(drained)
}
/// Remove all elements in the object map that do not return `true` when applied the `filter` function and
/// return them as a new object map.
///
/// # Function Parameters
///
/// * `key`: current key
/// * `value` _(optional)_: copy of element (bound to `this` if omitted)
///
/// # Example
///
/// ```rhai
/// let x = #{a:1, b:2, c:3, d:4, e:5};
///
/// let y = x.retain(|k| this < 3);
///
/// print(x); // prints #{"a":1, "b"2}
///
/// print(y); // prints #{"c":3, "d":4, "e":5]
///
/// let z = y.retain(|k, v| k == "c" || v >= 5);
///
/// print(y); // prints #{"c":3, "e":5}
///
/// print(z); // prints #{"d":4}
/// ```
#[rhai_fn(return_raw)]
pub fn retain(ctx: NativeCallContext, map: &mut Map, filter: FnPtr) -> RhaiResultOf<Map> {
if map.is_empty() {
return Ok(Map::new());
}

let mut drained = Map::new();
let mut retained = Map::new();

for (key, mut value) in mem::take(map).into_iter() {
if filter
.call_raw_with_extra_args(
"retain",
&ctx,
Some(&mut value),
[key.clone().into()],
[],
Some(1),
)?
.as_bool()
.unwrap_or(false)
{
retained.insert(key, value);
} else {
drained.insert(key, value);
}
}

*map = retained;

Ok(drained)
}

/// Return the JSON representation of the object map.
///
/// # Data types
Expand Down
24 changes: 21 additions & 3 deletions src/types/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,6 @@ impl fmt::Debug for Dynamic {
dict: &mut HashSet<*const Dynamic>,
) -> fmt::Result {
match value.0 {
#[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) {
Some(v) => {
if dict.insert(value) {
Expand Down Expand Up @@ -1267,8 +1266,27 @@ impl Dynamic {
}

#[cfg(not(feature = "no_closure"))]
Union::Shared(ref cell, ..) => {
crate::func::locked_read(cell).map_or(false, |v| v.is_hashable())
Union::Shared(..) => {
#[cfg(feature = "no_std")]
use hashbrown::HashSet;
#[cfg(not(feature = "no_std"))]
use std::collections::HashSet;

// Avoid infinite recursion for shared values in a reference loop.
fn checked_is_hashable(
value: &Dynamic,
dict: &mut HashSet<*const Dynamic>,
) -> bool {
match value.0 {
Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) {
Some(v) => dict.insert(value) && checked_is_hashable(&v, dict),
_ => false,
},
_ => value.is_hashable(),
}
}

checked_is_hashable(self, &mut <_>::default())
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions tests/build_type.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg(not(feature = "no_object"))]
use rhai::{CustomType, Engine, EvalAltResult, Position, TypeBuilder, INT};
use std::cmp::Ordering;

#[test]
fn test_build_type() {
Expand Down Expand Up @@ -32,7 +33,7 @@
fn set_z(&mut self, z: INT) {
self.z = z
}
fn get_component(&mut self, idx: INT) -> Result<INT, Box<EvalAltResult>> {

Check warning on line 36 in tests/build_type.rs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, --features testing-environ,no_index,serde,metadata,internals,debugging, sta...

method `get_component` is never used
match idx {
0 => Ok(self.x),
1 => Ok(self.y),
Expand Down Expand Up @@ -219,3 +220,31 @@
}
);
}

#[test]
fn test_build_type_operators() {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct XYZ(INT);

impl CustomType for XYZ {
fn build(mut tb: TypeBuilder<Self>) {
tb.with_fn("new_xyz", XYZ)
.with_fn("<", |a: XYZ, b: XYZ| Self::cmp(&a, &b) == Ordering::Less)
.with_fn(">", |a: XYZ, b: XYZ| Self::cmp(&a, &b) == Ordering::Greater)
.with_fn("<=", |a: XYZ, b: XYZ| Self::cmp(&a, &b) != Ordering::Greater)
.with_fn(">=", |a: XYZ, b: XYZ| Self::cmp(&a, &b) != Ordering::Less)
.with_fn("!=", |a: XYZ, b: XYZ| Self::cmp(&a, &b) != Ordering::Equal)
.with_fn("==", |a: XYZ, b: XYZ| Self::cmp(&a, &b) == Ordering::Equal);
}
}

let mut engine = Engine::new();
engine.build_type::<XYZ>();

assert!(!engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a == b").unwrap());
assert!(engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a != b").unwrap());
assert!(engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a < b").unwrap());
assert!(engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a <= b").unwrap());
assert!(!engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a > b").unwrap());
assert!(!engine.eval::<bool>("let a = new_xyz(1); let b = new_xyz(2); a >= b").unwrap());
}
Loading