diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 8a822e8ca..c3bba4cda 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -797,9 +797,9 @@ where /// Sort array storage. /// -/// This function expects it's values to have been pre-enumerated. They will be -/// sorted in-place. It is the caller's responsibility to place the resulting -/// half of the sorted array wherever. +/// This function expects it's values to have been pre-enumerated and +/// pre-resolved. They will be sorted in-place. It is the caller's +/// responsibility to place the resulting half of the sorted array wherever. /// /// This function will reverse the sort order if `Descending` sort is requested. /// @@ -810,7 +810,7 @@ where /// contents of the `values` array will be sorted in a random order. fn sort_inner<'a, 'gc, 'ctxt, C>( activation: &mut Activation<'a, 'gc, 'ctxt>, - values: &mut [(usize, Option>)], + values: &mut [(usize, Value<'gc>)], options: EnumSet, mut sort_func: C, ) -> Result @@ -821,8 +821,8 @@ where let mut error_signal = Ok(()); values.sort_unstable_by(|(_a_index, a), (_b_index, b)| { - let unresolved_a = a.clone().unwrap_or(Value::Undefined); - let unresolved_b = b.clone().unwrap_or(Value::Undefined); + let unresolved_a = a.clone(); + let unresolved_b = b.clone(); if matches!(unresolved_a, Value::Undefined) && matches!(unresolved_b, Value::Undefined) { unique_sort_satisfied = false; @@ -833,11 +833,7 @@ where return Ordering::Less; } - match sort_func( - activation, - a.clone().unwrap_or(Value::Undefined), - b.clone().unwrap_or(Value::Undefined), - ) { + match sort_func(activation, a.clone(), b.clone()) { Ok(Ordering::Equal) => { unique_sort_satisfied = false; Ordering::Equal @@ -897,6 +893,86 @@ fn compare_numeric<'gc>( } } +/// Take a sorted set of values and produce the result requested by the caller. +fn sort_postprocess<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + options: EnumSet, + unique_satisfied: bool, + values: Vec<(usize, Value<'gc>)>, +) -> Result, Error> { + if unique_satisfied { + if options.contains(SortOptions::ReturnIndexedArray) { + return build_array( + activation, + ArrayStorage::from_storage( + values + .iter() + .map(|(i, _v)| Some(i.clone().into())) + .collect(), + ), + ); + } else { + if let Some(mut old_array) = this.as_array_storage_mut(activation.context.gc_context) { + let mut new_vec = Vec::new(); + + for (src, v) in values.iter() { + if old_array.get(*src).is_none() && !matches!(v, Value::Undefined) { + new_vec.push(Some(v.clone())); + } else { + new_vec.push(old_array.get(*src).clone()); + } + } + + let mut new_array = ArrayStorage::from_storage(new_vec); + + swap(&mut *old_array, &mut new_array); + } + + return Ok(this.into()); + } + } + + Ok(0.into()) +} + +/// Given a value, extract it's array values. +/// +/// If the value is not an array, this function yields `None`. +fn extract_array_values<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + value: Value<'gc>, +) -> Result>>, Error> { + let object = value.coerce_to_object(activation).ok(); + let holey_vec = if let Some(object) = object { + if let Some(field_array) = object.as_array_storage() { + let mut array = Vec::new(); + + for v in field_array.iter() { + array.push(v); + } + + array + } else { + return Ok(None); + } + } else { + return Ok(None); + }; + + let mut unholey_vec = Vec::new(); + for (i, v) in holey_vec.iter().enumerate() { + unholey_vec.push(resolve_array_hole( + activation, + object.unwrap(), + i, + v.clone(), + )?); + } + + Ok(Some(unholey_vec)) +} + /// Impl `Array.sort` pub fn sort<'gc>( activation: &mut Activation<'_, 'gc, '_>, @@ -927,16 +1003,17 @@ pub fn sort<'gc>( ) }; - let mut values = if let Some(array) = this.as_array_storage() { - array + let mut values = if let Some(values) = extract_array_values(activation, this.into())? { + values .iter() .enumerate() - .collect::>)>>() + .map(|(i, v)| (i, v.clone())) + .collect::)>>() } else { return Ok(0.into()); }; - let unique_satisified = if let Some(v) = compare_fnc { + let unique_satisfied = if let Some(v) = compare_fnc { sort_inner( activation, &mut values, @@ -973,29 +1050,145 @@ pub fn sort<'gc>( )? }; - if unique_satisified { - if options.contains(SortOptions::ReturnIndexedArray) { - return build_array( - activation, - ArrayStorage::from_storage( - values - .iter() - .map(|(i, _v)| Some(i.clone().into())) - .collect(), - ), - ); + return sort_postprocess(activation, this, options, unique_satisfied, values); + } + + Ok(0.into()) +} + +/// Given a value, extract it's array values. +/// +/// If the value is not an array, it will be returned as if it was present in a +/// one-element array containing itself. This is intended for use with parsing +/// parameters which are optionally arrays. +fn extract_maybe_array_values<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + value: Value<'gc>, +) -> Result>, Error> { + Ok(extract_array_values(activation, value.clone())?.unwrap_or_else(|| vec![value])) +} + +/// Given a value, extract it's array values and coerce them to strings. +/// +/// If the value is not an array, it will be returned as if it was present in a +/// one-element array containing itself. This is intended for use with parsing +/// parameters which are optionally arrays. The returned value will still be +/// coerced into a string in this case. +fn extract_maybe_array_strings<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + value: Value<'gc>, +) -> Result>, Error> { + let mut out = Vec::new(); + + for value in extract_maybe_array_values(activation, value)? { + out.push(value.coerce_to_string(activation)?); + } + + Ok(out) +} + +/// Given a value, extract it's array values and coerce them to enumsets. +/// +/// If the value is not an array, it will be returned as if it was present in a +/// one-element array containing itself. This is intended for use with parsing +/// parameters which are optionally arrays. The returned value will still be +/// coerced into a string in this case. +fn extract_maybe_array_enumsets<'gc, E>( + activation: &mut Activation<'_, 'gc, '_>, + value: Value<'gc>, +) -> Result>, Error> +where + E: EnumSetType, +{ + let mut out = Vec::new(); + + for value in extract_maybe_array_values(activation, value)? { + out.push(value.coerce_to_enumset(activation)?); + } + + Ok(out) +} + +/// Impl `Array.sortOn` +pub fn sort_on<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(this) = this { + if let Some(field_names_value) = args.get(0).cloned() { + let field_names = extract_maybe_array_strings(activation, field_names_value)?; + let mut options = extract_maybe_array_enumsets( + activation, + args.get(1).cloned().unwrap_or_else(|| 0.into()), + )?; + + let first_option = options + .get(0) + .cloned() + .unwrap_or_else(EnumSet::empty) + .intersection(SortOptions::UniqueSort | SortOptions::ReturnIndexedArray); + let mut values = if let Some(values) = extract_array_values(activation, this.into())? { + values + .iter() + .enumerate() + .map(|(i, v)| (i, v.clone())) + .collect::)>>() } else { - let mut new_array = - ArrayStorage::from_storage(values.iter().map(|(_i, v)| v.clone()).collect()); + return Ok(0.into()); + }; - if let Some(mut old_array) = - this.as_array_storage_mut(activation.context.gc_context) - { - swap(&mut *old_array, &mut new_array); - } - - return Ok(this.into()); + if options.len() < field_names.len() { + options.resize( + field_names.len(), + options.last().cloned().unwrap_or_else(EnumSet::empty), + ); } + + let unique_satisfied = sort_inner( + activation, + &mut values, + first_option, + constrain(|activation, a, b| { + for (field_name, options) in field_names.iter().zip(options.iter()) { + let mut a_object = a.coerce_to_object(activation)?; + let a_field = a_object.get_property( + a_object, + &QName::new(Namespace::public_namespace(), *field_name), + activation, + )?; + + let mut b_object = b.coerce_to_object(activation)?; + let b_field = b_object.get_property( + b_object, + &QName::new(Namespace::public_namespace(), *field_name), + activation, + )?; + + let ord = if options.contains(SortOptions::Numeric) { + compare_numeric(activation, a_field, b_field)? + } else if options.contains(SortOptions::CaseInsensitive) { + compare_string_case_insensitive(activation, a_field, b_field)? + } else { + compare_string_case_sensitive(activation, a_field, b_field)? + }; + + if matches!(ord, Ordering::Equal) { + continue; + } + + if options.contains(SortOptions::Descending) { + return Ok(ord.reverse()); + } else { + return Ok(ord); + } + } + + Ok(Ordering::Equal) + }), + )?; + + return sort_postprocess(activation, this, first_option, unique_satisfied, values); } } @@ -1117,6 +1310,11 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> Method::from_builtin(sort), )); + class.write(mc).define_instance_trait(Trait::from_method( + QName::new(Namespace::public_namespace(), "sortOn"), + Method::from_builtin(sort_on), + )); + class.write(mc).define_class_trait(Trait::from_const( QName::new(Namespace::public_namespace(), "CASEINSENSITIVE"), Multiname::from(QName::new(Namespace::public_namespace(), "uint")), diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index 2d7d01518..30705c996 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -380,6 +380,7 @@ swf_tests! { (as3_array_slice, "avm2/array_slice", 1), (as3_array_splice, "avm2/array_splice", 1), (as3_array_sort, "avm2/array_sort", 1), + (as3_array_sorton, "avm2/array_sorton", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/array_sorton/Test.as b/core/tests/swfs/avm2/array_sorton/Test.as new file mode 100644 index 000000000..d4d7069b3 --- /dev/null +++ b/core/tests/swfs/avm2/array_sorton/Test.as @@ -0,0 +1,218 @@ +package { + public class Test { + } +} + +function assert_array(a) { + for (var i = 0; i < a.length; i += 1) { + trace(a[i]); + } +} + +function assert_array_props(a) { + for (var i = 0; i < a.length; i += 1) { + if (a[i] !== undefined && a[i] !== null) { + trace(a[i].numprop); + trace(a[i].strprop); + } else { + trace("//(undefined value omitted)"); + } + } +} + +function fresh_array_a() { + trace("//var a = new Array(item1, item2, item3)"); + var a = new Array(item1, item2, item3); + + trace("//a[4] = item5;"); + a[4] = item5; + + return a; +} + +function test_holes(a) { + var item4 = {"numprop": 9, "strprop": "boo", "numprop2": 4}; + + trace("//Array.prototype[2] = \"hole10\";"); + Array.prototype[2] = "hole10"; + + trace("//Array.prototype[3] = \"hole11\";"); + Array.prototype[3] = "hole11"; + + trace("//Array.prototype[4] = \"hole12\";"); + Array.prototype[4] = "hole12"; + + trace("//(properties of contents of a)"); + assert_array_props(a); + + trace("//(cleaning up our holes...)"); + + delete Array.prototype[2]; + delete Array.prototype[4]; + + trace("//Array.prototype[3] = item4;"); + Array.prototype[3] = item4; +} + +trace("//var item1 = {\"numprop\": 3, \"strprop\": \"Abc\", \"numprop2\": 3}"); +var item1 = {"numprop": 5, "strprop": "Abc", "numprop2": 3}; + +trace("//var item2 = {\"numprop\": 3, \"strprop\": \"Azc\", \"numprop2\": 2}"); +var item2 = {"numprop": 3, "strprop": "Azc", "numprop2": 2}; + +trace("//var item3 = {\"numprop\": 3, \"strprop\": \"aXc\", \"numprop2\": 1}"); +var item3 = {"numprop": 7, "strprop": "aXc", "numprop2": 1}; + +trace("//var item4 = {\"numprop\": 3, \"strprop\": \"boo\", \"numprop2\": 4}"); +var item4 = {"numprop": 9, "strprop": "boo", "numprop2": 4}; + +trace("//var item5 = {\"numprop\": 5, \"strprop\": \"bool\", \"numprop2\": \"5\"}"); +var item5 = {"numprop": 11, "strprop": "bool", "numprop2": "5"}; + +var a = fresh_array_a(); + +trace("//Array.prototype[3] = item4;"); +Array.prototype[3] = item4; + +trace("//a.sortOn(\"numprop\", Array.UNIQUESORT) === 0"); +trace(a.sortOn("numprop", Array.UNIQUESORT) === 0); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"]))"); +assert_array_props(a.sortOn(["numprop", "strprop"])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE))"); +assert_array_props(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE)); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING))"); +assert_array_props(a.sortOn(["numprop", "strprop"], Array.DESCENDING)); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.CASEINSENSITIVE | Array.DESCENDING))"); +assert_array_props(a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING)); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.NUMERIC | Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.NUMERIC | Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.NUMERIC))"); +assert_array_props(a.sortOn(["numprop", "strprop"], Array.NUMERIC)); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY))"); +assert_array(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY)); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], Array.DESCENDING | Array.NUMERIC))"); +assert_array_props(a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC)); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, 0]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, 0])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, 0]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [0, 0])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.DESCENDING]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.DESCENDING])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.DESCENDING, 0]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [Array.DESCENDING, 0])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.CASEINSENSITIVE]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE, 0]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, 0])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [0, Array.CASEINSENSITIVE | Array.DESCENDING]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE | Array.DESCENDING])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.DESCENDING, Array.CASEINSENSITIVE]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [Array.DESCENDING, Array.CASEINSENSITIVE])); +test_holes(a); + +a = fresh_array_a(); + +trace("//(contents of a.sortOn([\"numprop\", \"strprop\"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING]))"); +assert_array(a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING])); + +trace("//(properties of contents of a.sortOn([\"numprop\", \"strprop\"], [Array.CASEINSENSITIVE, Array.DESCENDING]))"); +assert_array_props(a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, Array.DESCENDING])); +test_holes(a); + +a = fresh_array_a(); + +trace("//a.sortOn([\"strprop\", \"numprop\"], [Array.NUMERIC, Array.UNIQUESORT]) === 0"); +trace(a.sortOn(["strprop", "numprop"], [Array.NUMERIC, Array.UNIQUESORT]) === 0); \ No newline at end of file diff --git a/core/tests/swfs/avm2/array_sorton/output.txt b/core/tests/swfs/avm2/array_sorton/output.txt new file mode 100644 index 000000000..b943b647e --- /dev/null +++ b/core/tests/swfs/avm2/array_sorton/output.txt @@ -0,0 +1,539 @@ +//var item1 = {"numprop": 3, "strprop": "Abc", "numprop2": 3} +//var item2 = {"numprop": 3, "strprop": "Azc", "numprop2": 2} +//var item3 = {"numprop": 3, "strprop": "aXc", "numprop2": 1} +//var item4 = {"numprop": 3, "strprop": "boo", "numprop2": 4} +//var item5 = {"numprop": 5, "strprop": "bool", "numprop2": "5"} +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//Array.prototype[3] = item4; +//a.sortOn("numprop", Array.UNIQUESORT) === 0 +false +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.RETURNINDEXEDARRAY)) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.RETURNINDEXEDARRAY)) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE)) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.RETURNINDEXEDARRAY)) +3 +2 +0 +1 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING)) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING | Array.RETURNINDEXEDARRAY)) +3 +2 +0 +1 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], Array.CASEINSENSITIVE | Array.DESCENDING)) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.NUMERIC | Array.RETURNINDEXEDARRAY)) +1 +0 +2 +3 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], Array.NUMERIC)) +3 +Azc +5 +Abc +7 +aXc +9 +boo +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +3 +Azc +5 +Abc +7 +aXc +9 +boo +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC | Array.RETURNINDEXEDARRAY)) +4 +3 +2 +0 +1 +//(properties of contents of a.sortOn(["numprop", "strprop"], Array.DESCENDING | Array.NUMERIC)) +11 +bool +9 +boo +7 +aXc +5 +Abc +3 +Azc +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +9 +boo +7 +aXc +5 +Abc +3 +Azc +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, 0])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [0, 0])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.DESCENDING])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.DESCENDING])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, 0])) +3 +2 +0 +1 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.DESCENDING, 0])) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, 0])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, 0])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY, Array.CASEINSENSITIVE | Array.DESCENDING])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [0, Array.CASEINSENSITIVE | Array.DESCENDING])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE | Array.DESCENDING, 0])) +3 +2 +0 +1 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE | Array.DESCENDING, 0])) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.DESCENDING, Array.CASEINSENSITIVE])) +3 +2 +0 +1 +4 +//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.DESCENDING, Array.CASEINSENSITIVE])) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +9 +boo +7 +aXc +5 +Abc +3 +Azc +11 +bool +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//(contents of a.sortOn(["numprop", "strprop"], [Array.RETURNINDEXEDARRAY | Array.CASEINSENSITIVE, Array.DESCENDING])) +4 +1 +0 +2 +3 +//(properties of contents of a.sortOn(["numprop", "strprop"], [Array.CASEINSENSITIVE, Array.DESCENDING])) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//Array.prototype[2] = "hole10"; +//Array.prototype[3] = "hole11"; +//Array.prototype[4] = "hole12"; +//(properties of contents of a) +11 +bool +3 +Azc +5 +Abc +7 +aXc +9 +boo +//(cleaning up our holes...) +//Array.prototype[3] = item4; +//var a = new Array(item1, item2, item3) +//a[4] = item5; +//a.sortOn(["strprop", "numprop"], [Array.NUMERIC, Array.UNIQUESORT]) === 0 +false diff --git a/core/tests/swfs/avm2/array_sorton/test.fla b/core/tests/swfs/avm2/array_sorton/test.fla new file mode 100644 index 000000000..b27a5e649 Binary files /dev/null and b/core/tests/swfs/avm2/array_sorton/test.fla differ diff --git a/core/tests/swfs/avm2/array_sorton/test.swf b/core/tests/swfs/avm2/array_sorton/test.swf new file mode 100644 index 000000000..613578015 Binary files /dev/null and b/core/tests/swfs/avm2/array_sorton/test.swf differ