Skip to content

Commit 138b1fb

Browse files
authored
Merge pull request #1058 from schungx/master
Fix panic in sorting.
2 parents 5361de3 + 6bb778e commit 138b1fb

File tree

3 files changed

+101
-30
lines changed

3 files changed

+101
-30
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ Breaking Changes
99

1010
* `stdweb` support is removed. The feature flag `stdweb` is also removed. Use `wasm-bindgen` instead.
1111

12+
Bug fixes
13+
---------
14+
15+
* (Fuzzing) Fixed panic when using `sort` on an array with a comparer function that does not implement a [total order](https://en.wikipedia.org/wiki/Total_order).
1216

1317
Enhancements
1418
------------
1519

1620
* The optimizer now optimizes constant object map property accesses (thanks [`@phsym`](https://github.com/phsym) [#1050](https://github.com/rhaiscript/rhai/pull/1050)).
21+
* Optimization is added to turn `if cond1 { if cond2 { ... } }` into `if cond1 && cond2 { ... }`.
1722
* The method `sort` for arrays is also aliased to `sort_by`.
1823
* The methods `sort_desc`, `order`, `order_by` and `order_desc` are added to arrays.
24+
* An exception is now thrown when attempting to use `sort` on an array containing unsupported element types.
1925

2026

2127
Version 1.23.6

src/optimizer.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::func::hashing::get_hasher;
1515
use crate::tokenizer::Token;
1616
use crate::{
1717
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, Position,
18-
Scope, AST,
18+
Scope, StaticVec, AST,
1919
};
2020
#[cfg(feature = "no_std")]
2121
use std::prelude::v1::*;
@@ -498,6 +498,37 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
498498
}
499499
}
500500
}
501+
// if expr1 { if expr2 { ... } } -> if expr1 && expr2 { ... }
502+
Stmt::If(x, pos)
503+
if x.branch.is_empty()
504+
&& x.body.len() == 1
505+
&& matches!(&x.body.as_ref()[0], Stmt::If(x2, ..) if x2.branch.is_empty()) =>
506+
{
507+
let Stmt::If(mut x2, ..) = x.body.as_mut()[0].take() else {
508+
unreachable!()
509+
};
510+
511+
state.set_dirty();
512+
let body = x2.body.take_statements();
513+
*x.body.statements_mut() =
514+
optimize_stmt_block(body, state, preserve_result, true, false);
515+
516+
let mut expr2 = x2.expr.take();
517+
optimize_expr(&mut expr2, state, false);
518+
519+
if let Expr::And(ref mut v, ..) = x.expr {
520+
v.push(expr2);
521+
} else {
522+
let mut expr1 = x.expr.take();
523+
optimize_expr(&mut expr1, state, false);
524+
525+
let mut v = StaticVec::new_const();
526+
v.push(expr1);
527+
v.push(expr2);
528+
529+
x.expr = Expr::And(v.into(), *pos);
530+
}
531+
}
501532
// if expr { if_block } else { else_block }
502533
Stmt::If(x, ..) => {
503534
let FlowControl { expr, body, branch } = &mut **x;

src/packages/array_basic.rs

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,8 @@ pub mod array_functions {
15141514
}
15151515
/// Sort the array based on applying the `comparer` function.
15161516
///
1517+
/// The `comparer` function must implement a [total order](https://en.wikipedia.org/wiki/Total_order).
1518+
///
15171519
/// # Function Parameters
15181520
///
15191521
/// * `element1`: copy of the current array element to compare
@@ -1534,6 +1536,11 @@ pub mod array_functions {
15341536
///
15351537
/// Any other return value type will yield unpredictable order.
15361538
///
1539+
/// # Errors
1540+
///
1541+
/// If the `comparer` function does not implement a [total order](https://en.wikipedia.org/wiki/Total_order),
1542+
/// an error is returned.
1543+
///
15371544
/// # Example
15381545
///
15391546
/// ```rhai
@@ -1544,31 +1551,38 @@ pub mod array_functions {
15441551
///
15451552
/// print(x); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
15461553
/// ```
1547-
#[rhai_fn(name = "sort", name = "sort_by")]
1548-
pub fn sort_by(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) {
1554+
#[rhai_fn(name = "sort", name = "sort_by", return_raw)]
1555+
pub fn sort_by(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> RhaiResultOf<()> {
15491556
if array.len() <= 1 {
1550-
return;
1557+
return Ok(());
15511558
}
15521559

1553-
array.sort_by(|x, y| {
1554-
comparer
1555-
.call_raw(&ctx, None, [x.clone(), y.clone()])
1556-
.ok()
1557-
.and_then(|v| {
1558-
v.as_int()
1559-
.or_else(|_| v.as_bool().map(|v| if v { -1 } else { 1 }))
1560-
.ok()
1561-
})
1562-
.map_or_else(
1563-
|| x.type_id().cmp(&y.type_id()),
1564-
|v| match v {
1565-
v if v > 0 => Ordering::Greater,
1566-
v if v < 0 => Ordering::Less,
1567-
0 => Ordering::Equal,
1568-
_ => unreachable!("v is {}", v),
1569-
},
1570-
)
1571-
});
1560+
// `slice::sort_by` may panic if the comparer function does not implement a total order.
1561+
// Catch this instead.
1562+
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1563+
array.sort_by(|x, y| {
1564+
comparer
1565+
.call_raw(&ctx, None, [x.clone(), y.clone()])
1566+
.ok()
1567+
.and_then(|v| {
1568+
v.as_int()
1569+
.or_else(|_| v.as_bool().map(|v| if v { -1 } else { 1 }))
1570+
.ok()
1571+
})
1572+
.map_or_else(
1573+
|| x.type_id().cmp(&y.type_id()),
1574+
|v| match v {
1575+
v if v > 0 => Ordering::Greater,
1576+
v if v < 0 => Ordering::Less,
1577+
0 => Ordering::Equal,
1578+
_ => unreachable!("v is {}", v),
1579+
},
1580+
)
1581+
});
1582+
}))
1583+
.map_err(|_| {
1584+
ERR::ErrorRuntime("error in comparer for sorting".into(), Position::NONE).into()
1585+
})
15721586
}
15731587
/// Sort the array.
15741588
///
@@ -1603,7 +1617,7 @@ pub mod array_functions {
16031617

16041618
if array.iter().any(|a| a.type_id() != type_id) {
16051619
return Err(ERR::ErrorFunctionNotFound(
1606-
"sort() cannot be called with elements of different types".into(),
1620+
"elements of different types cannot be sorted".into(),
16071621
Position::NONE,
16081622
)
16091623
.into());
@@ -1663,7 +1677,11 @@ pub mod array_functions {
16631677
return Ok(());
16641678
}
16651679

1666-
Ok(())
1680+
Err(ERR::ErrorFunctionNotFound(
1681+
format!("elements of {} cannot be sorted", array[0].type_name()).into(),
1682+
Position::NONE,
1683+
)
1684+
.into())
16671685
}
16681686
/// Sort the array in descending order.
16691687
///
@@ -1698,7 +1716,7 @@ pub mod array_functions {
16981716

16991717
if array.iter().any(|a| a.type_id() != type_id) {
17001718
return Err(ERR::ErrorFunctionNotFound(
1701-
"sort_desc() cannot be called with elements of different types".into(),
1719+
"elements of different types cannot be sorted".into(),
17021720
Position::NONE,
17031721
)
17041722
.into());
@@ -1782,10 +1800,16 @@ pub mod array_functions {
17821800
return Ok(());
17831801
}
17841802

1785-
Ok(())
1803+
Err(ERR::ErrorFunctionNotFound(
1804+
format!("elements of {} cannot be sorted", array[0].type_name()).into(),
1805+
Position::NONE,
1806+
)
1807+
.into())
17861808
}
17871809
/// Sort the array based on applying the `comparer` function and return it as a new array.
17881810
///
1811+
/// The `comparer` function must implement a [total order](https://en.wikipedia.org/wiki/Total_order).
1812+
///
17891813
/// # Function Parameters
17901814
///
17911815
/// * `element1`: copy of the current array element to compare
@@ -1806,6 +1830,11 @@ pub mod array_functions {
18061830
///
18071831
/// Any other return value type will yield unpredictable order.
18081832
///
1833+
/// # Errors
1834+
///
1835+
/// If the `comparer` function does not implement a [total order](https://en.wikipedia.org/wiki/Total_order),
1836+
/// an error is returned.
1837+
///
18091838
/// # Example
18101839
///
18111840
/// ```rhai
@@ -1816,10 +1845,15 @@ pub mod array_functions {
18161845
///
18171846
/// print(y); // prints "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]"
18181847
/// ```
1819-
pub fn order_by(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> Array {
1848+
#[rhai_fn(name = "order", name = "order_by", return_raw)]
1849+
pub fn order_by(
1850+
ctx: NativeCallContext,
1851+
array: &mut Array,
1852+
comparer: FnPtr,
1853+
) -> RhaiResultOf<Array> {
18201854
let mut array = array.clone();
1821-
sort_by(ctx, &mut array, comparer);
1822-
array
1855+
sort_by(ctx, &mut array, comparer)?;
1856+
Ok(array)
18231857
}
18241858
/// Sort the array and return it as a new array.
18251859
///

0 commit comments

Comments
 (0)