Skip to content

Commit

Permalink
Merge pull request #888 from schungx/master
Browse files Browse the repository at this point in the history
Fix stack overflow
  • Loading branch information
schungx authored Jun 15, 2024
2 parents 7ea07e7 + 112e6e4 commit a407edb
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 6 deletions.
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 @@ -219,3 +220,31 @@ fn test_build_type_macro() {
}
);
}

#[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());
}

0 comments on commit a407edb

Please sign in to comment.