From 1cf81c41d55f222f2f31d7fa63666aca24de4fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Mon, 14 Jul 2025 21:47:16 +0200 Subject: [PATCH 01/13] avm2: improve coercion to function error for Vector.some and Array.some --- core/src/avm2/error.rs | 29 +++++++++++++ core/src/avm2/filters.rs | 41 ++++++++----------- core/src/avm2/globals/array.rs | 2 +- core/src/avm2/globals/vector.rs | 2 +- core/src/avm2/value.rs | 36 ++++++++++------ .../from_avmplus/as3/Vector/some/test.toml | 1 - .../as3/Vector/some_initializers/test.toml | 1 - 7 files changed, 73 insertions(+), 39 deletions(-) diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index 02580a947733..74ab683065fc 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -168,6 +168,16 @@ pub fn make_error_1003<'gc>(activation: &mut Activation<'_, 'gc>, radix: i32) -> } } +#[inline(never)] +#[cold] +pub fn make_error_1006<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> { + let err = type_error(activation, "Error #1006: value is not a function.", 1006); + match err { + Ok(err) => Error::avm_error(err), + Err(err) => err, + } +} + #[inline(never)] #[cold] pub fn make_error_1010<'gc>( @@ -263,6 +273,25 @@ pub fn make_error_1033<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> } } +#[inline(never)] +#[cold] +pub fn make_error_1034<'gc>( + activation: &mut Activation<'_, 'gc>, + object: impl Debug, + class_name: impl Display, +) -> Error<'gc> { + let err = type_error( + activation, + &format!("Error #1034: Type Coercion failed: cannot convert {object:?} to {class_name}."), + 1034, + ); + + match err { + Ok(err) => Error::avm_error(err), + Err(err) => err, + } +} + pub fn make_error_1053<'gc>( activation: &mut Activation<'_, 'gc>, trait_name: AvmString<'gc>, diff --git a/core/src/avm2/filters.rs b/core/src/avm2/filters.rs index de7e6faed173..b24ac442e9ac 100644 --- a/core/src/avm2/filters.rs +++ b/core/src/avm2/filters.rs @@ -1,4 +1,4 @@ -use crate::avm2::error::{make_error_2008, type_error}; +use crate::avm2::error::{make_error_1034, make_error_2008}; use crate::avm2::globals::flash::display::shader_job::get_shader_args; use crate::avm2::globals::slots::flash_filters_bevel_filter as bevel_filter_slots; use crate::avm2::globals::slots::flash_filters_blur_filter as blur_filter_slots; @@ -168,13 +168,11 @@ impl FilterAvm2Ext for Filter { )?)); } - Err(Error::avm_error(type_error( + Err(make_error_1034( activation, - &format!( - "Error #1034: Type Coercion failed: cannot convert {object:?} to flash.filters.BitmapFilter." - ), - 1034, - )?)) + object, + "flash.filters.BitmapFilter", + )) } fn as_avm2_object<'gc>( @@ -498,23 +496,20 @@ fn avm2_to_displacement_map_filter<'gc>( let scale_y = object .get_slot(displacement_map_filter_slots::SCALE_Y) .coerce_to_number(activation)?; - let map_bitmap = if let Value::Object(bitmap) = - object.get_slot(displacement_map_filter_slots::MAP_BITMAP) - { - if let Some(bitmap) = bitmap.as_bitmap_data() { - Some(bitmap.bitmap_handle(activation.gc(), activation.context.renderer)) + let map_bitmap = + if let Value::Object(bitmap) = object.get_slot(displacement_map_filter_slots::MAP_BITMAP) { + if let Some(bitmap) = bitmap.as_bitmap_data() { + Some(bitmap.bitmap_handle(activation.gc(), activation.context.renderer)) + } else { + return Err(make_error_1034( + activation, + bitmap, + "flash.display.BitmapData", + )); + } } else { - return Err(Error::avm_error(type_error( - activation, - &format!( - "Error #1034: Type Coercion failed: cannot convert {bitmap:?} to flash.display.BitmapData." - ), - 1034, - )?)); - } - } else { - None - }; + None + }; Ok(Filter::DisplacementMapFilter(DisplacementMapFilter { color: Color::from_rgb(color, (alpha * 255.0) as u8), component_x: component_x as u8, diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 39f34ac4b7a9..cea1419a8fd1 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -408,7 +408,7 @@ pub fn some<'gc>( while let Some((i, item)) = iter.next(activation)? { let result = callback - .call(activation, receiver, &[item, i.into(), this.into()])? + .coerce_and_call(activation, receiver, &[item, i.into(), this.into()])? .coerce_to_boolean(); if result { diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 095b9dd44aa1..ac3a5a4adc7e 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -301,7 +301,7 @@ pub fn some<'gc>( while let Some((i, item)) = iter.next(activation)? { let result = callback - .call(activation, receiver, &[item, i.into(), this.into()])? + .coerce_and_call(activation, receiver, &[item, i.into(), this.into()])? .coerce_to_boolean(); if result { diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 64f44a18956d..167be25d311c 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1,8 +1,8 @@ //! AVM2 values use crate::avm2::activation::Activation; -use crate::avm2::error; -use crate::avm2::error::type_error; +use crate::avm2::error::{self}; +use crate::avm2::error::{make_error_1006, type_error}; use crate::avm2::function::{exec, FunctionArgs}; use crate::avm2::object::{NamespaceObject, Object, TObject}; use crate::avm2::property::Property; @@ -1175,11 +1175,7 @@ impl<'gc> Value<'gc> { if let Some(value) = dynamic_lookup { value.call(activation, *self, arguments) } else { - Err(Error::avm_error(type_error( - activation, - "Error #1006: value is not a function.", - 1006, - )?)) + Err(make_error_1006(activation)) } } } @@ -1364,11 +1360,27 @@ impl<'gc> Value<'gc> { Some(Object::FunctionObject(function_object)) => { function_object.call(activation, receiver, args) } - _ => Err(Error::avm_error(type_error( - activation, - "Error #1006: value is not a function.", - 1006, - )?)), + _ => Err(make_error_1006(activation)), + } + } + + /// Same as `call`, but producing a 1034 error + pub fn coerce_and_call( + &self, + activation: &mut Activation<'_, 'gc>, + receiver: Value<'gc>, + args: &[Value<'gc>], + ) -> Result, Error<'gc>> { + let function_class = activation.avm2().class_defs().function; + + let fn_value = self.coerce_to_type(activation, function_class)?; + + match fn_value.as_object() { + Some(Object::ClassObject(class_object)) => class_object.call(activation, args), + Some(Object::FunctionObject(function_object)) => { + function_object.call(activation, receiver, args) + } + _ => unreachable!("Value should have been coerced to function"), } } diff --git a/tests/tests/swfs/from_avmplus/as3/Vector/some/test.toml b/tests/tests/swfs/from_avmplus/as3/Vector/some/test.toml index 661aaf82f605..cf6123969a1d 100644 --- a/tests/tests/swfs/from_avmplus/as3/Vector/some/test.toml +++ b/tests/tests/swfs/from_avmplus/as3/Vector/some/test.toml @@ -1,2 +1 @@ num_ticks = 1 -known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12321 diff --git a/tests/tests/swfs/from_avmplus/as3/Vector/some_initializers/test.toml b/tests/tests/swfs/from_avmplus/as3/Vector/some_initializers/test.toml index 661aaf82f605..cf6123969a1d 100644 --- a/tests/tests/swfs/from_avmplus/as3/Vector/some_initializers/test.toml +++ b/tests/tests/swfs/from_avmplus/as3/Vector/some_initializers/test.toml @@ -1,2 +1 @@ num_ticks = 1 -known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12321 From fc1ce3ccb1535078b41f79de5f47487ed9c36122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Mon, 14 Jul 2025 22:22:11 +0200 Subject: [PATCH 02/13] refactor --- core/src/avm2/globals/array.rs | 2 +- core/src/avm2/globals/vector.rs | 18 +++++++++++---- core/src/avm2/value.rs | 41 ++++++++++++++++----------------- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index cea1419a8fd1..39f34ac4b7a9 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -408,7 +408,7 @@ pub fn some<'gc>( while let Some((i, item)) = iter.next(activation)? { let result = callback - .coerce_and_call(activation, receiver, &[item, i.into(), this.into()])? + .call(activation, receiver, &[item, i.into(), this.into()])? .coerce_to_boolean(); if result { diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index ac3a5a4adc7e..5c79c4314df0 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -270,7 +270,9 @@ pub fn every<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { + return Ok(false.into()); + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; @@ -295,13 +297,15 @@ pub fn some<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { + return Ok(false.into()); + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; while let Some((i, item)) = iter.next(activation)? { let result = callback - .coerce_and_call(activation, receiver, &[item, i.into(), this.into()])? + .call(activation, receiver, &[item, i.into(), this.into()])? .coerce_to_boolean(); if result { @@ -320,7 +324,9 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { + return Ok(false.into()); + }; let receiver = args.get_value(1); let value_type = this @@ -351,7 +357,9 @@ pub fn for_each<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { + return Ok(false.into()); + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 167be25d311c..ceee80304d13 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -4,7 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::{self}; use crate::avm2::error::{make_error_1006, type_error}; use crate::avm2::function::{exec, FunctionArgs}; -use crate::avm2::object::{NamespaceObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, NamespaceObject, Object, TObject}; use crate::avm2::property::Property; use crate::avm2::script::TranslationUnit; use crate::avm2::vtable::VTable; @@ -818,6 +818,25 @@ impl<'gc> Value<'gc> { }) } + pub fn coerce_to_function( + &self, + activation: &mut Activation<'_, 'gc>, + ) -> Result>, Error<'gc>> { + match self { + Value::Null | Value::Undefined => Ok(None), + _ => { + let function_class = activation.avm2().class_defs().function; + + let fn_value = self.coerce_to_type(activation, function_class)?; + + match fn_value.as_object().and_then(|o| o.as_function_object()) { + Some(function_object) => Ok(Some(function_object)), + _ => unreachable!("Value should have been coerced to function"), + } + } + } + } + /// Coerce the value to a literal value / debug string. /// /// This matches the string formatting that appears to be in use in "debug" @@ -1364,26 +1383,6 @@ impl<'gc> Value<'gc> { } } - /// Same as `call`, but producing a 1034 error - pub fn coerce_and_call( - &self, - activation: &mut Activation<'_, 'gc>, - receiver: Value<'gc>, - args: &[Value<'gc>], - ) -> Result, Error<'gc>> { - let function_class = activation.avm2().class_defs().function; - - let fn_value = self.coerce_to_type(activation, function_class)?; - - match fn_value.as_object() { - Some(Object::ClassObject(class_object)) => class_object.call(activation, args), - Some(Object::FunctionObject(function_object)) => { - function_object.call(activation, receiver, args) - } - _ => unreachable!("Value should have been coerced to function"), - } - } - pub fn construct( &self, activation: &mut Activation<'_, 'gc>, From d0556b2ad41b8a07b91a61480240210a021b0631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Mon, 14 Jul 2025 22:33:52 +0200 Subject: [PATCH 03/13] simplify coercion logic --- core/src/avm2/value.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index ceee80304d13..c770e609df7d 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -4,7 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::{self}; use crate::avm2::error::{make_error_1006, type_error}; use crate::avm2::function::{exec, FunctionArgs}; -use crate::avm2::object::{FunctionObject, NamespaceObject, Object, TObject}; +use crate::avm2::object::{NamespaceObject, Object, TObject}; use crate::avm2::property::Property; use crate::avm2::script::TranslationUnit; use crate::avm2::vtable::VTable; @@ -821,18 +821,15 @@ impl<'gc> Value<'gc> { pub fn coerce_to_function( &self, activation: &mut Activation<'_, 'gc>, - ) -> Result>, Error<'gc>> { + ) -> Result>, Error<'gc>> { match self { Value::Null | Value::Undefined => Ok(None), _ => { let function_class = activation.avm2().class_defs().function; - let fn_value = self.coerce_to_type(activation, function_class)?; + let value = self.coerce_to_type(activation, function_class)?; - match fn_value.as_object().and_then(|o| o.as_function_object()) { - Some(function_object) => Ok(Some(function_object)), - _ => unreachable!("Value should have been coerced to function"), - } + Ok(Some(value)) } } } From fdce3d6b508c7bb12772a197aa2a50f1905cb2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Tue, 15 Jul 2025 09:21:53 +0200 Subject: [PATCH 04/13] refactor --- core/src/avm2/error.rs | 19 ------------------- core/src/avm2/filters.rs | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/core/src/avm2/error.rs b/core/src/avm2/error.rs index 74ab683065fc..87599d0a9c53 100644 --- a/core/src/avm2/error.rs +++ b/core/src/avm2/error.rs @@ -273,25 +273,6 @@ pub fn make_error_1033<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> } } -#[inline(never)] -#[cold] -pub fn make_error_1034<'gc>( - activation: &mut Activation<'_, 'gc>, - object: impl Debug, - class_name: impl Display, -) -> Error<'gc> { - let err = type_error( - activation, - &format!("Error #1034: Type Coercion failed: cannot convert {object:?} to {class_name}."), - 1034, - ); - - match err { - Ok(err) => Error::avm_error(err), - Err(err) => err, - } -} - pub fn make_error_1053<'gc>( activation: &mut Activation<'_, 'gc>, trait_name: AvmString<'gc>, diff --git a/core/src/avm2/filters.rs b/core/src/avm2/filters.rs index b24ac442e9ac..b73ae3af0d9f 100644 --- a/core/src/avm2/filters.rs +++ b/core/src/avm2/filters.rs @@ -1,4 +1,4 @@ -use crate::avm2::error::{make_error_1034, make_error_2008}; +use crate::avm2::error::make_error_2008; use crate::avm2::globals::flash::display::shader_job::get_shader_args; use crate::avm2::globals::slots::flash_filters_bevel_filter as bevel_filter_slots; use crate::avm2::globals::slots::flash_filters_blur_filter as blur_filter_slots; @@ -168,11 +168,7 @@ impl FilterAvm2Ext for Filter { )?)); } - Err(make_error_1034( - activation, - object, - "flash.filters.BitmapFilter", - )) + unreachable!("{object:?} must be of type BitmapFilter") } fn as_avm2_object<'gc>( @@ -498,15 +494,12 @@ fn avm2_to_displacement_map_filter<'gc>( .coerce_to_number(activation)?; let map_bitmap = if let Value::Object(bitmap) = object.get_slot(displacement_map_filter_slots::MAP_BITMAP) { - if let Some(bitmap) = bitmap.as_bitmap_data() { - Some(bitmap.bitmap_handle(activation.gc(), activation.context.renderer)) - } else { - return Err(make_error_1034( - activation, - bitmap, - "flash.display.BitmapData", - )); - } + Some( + bitmap + .as_bitmap_data() + .unwrap() + .bitmap_handle(activation.gc(), activation.context.renderer), + ) } else { None }; From 2924c32886742140bc39a221350971a714235a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 20:57:32 +0200 Subject: [PATCH 05/13] refactor implementation --- core/src/avm2/globals/Array.as | 6 +- core/src/avm2/globals/VectorDouble.as | 6 +- core/src/avm2/globals/VectorInt.as | 6 +- core/src/avm2/globals/VectorObject.as | 6 +- core/src/avm2/globals/VectorUint.as | 6 +- core/src/avm2/globals/array.rs | 28 ++++++-- core/src/avm2/globals/vector.rs | 88 +++++--------------------- core/src/avm2/globals/vector_double.rs | 2 +- core/src/avm2/globals/vector_int.rs | 2 +- core/src/avm2/globals/vector_object.rs | 2 +- core/src/avm2/globals/vector_uint.rs | 2 +- core/src/avm2/value.rs | 16 ----- 12 files changed, 65 insertions(+), 105 deletions(-) diff --git a/core/src/avm2/globals/Array.as b/core/src/avm2/globals/Array.as index 627d8b28a409..2de461a527d1 100644 --- a/core/src/avm2/globals/Array.as +++ b/core/src/avm2/globals/Array.as @@ -193,7 +193,9 @@ package { AS3 native function slice(start:* = 0, end:* = 4294967295):Array; - AS3 native function some(callback:Function, receiver:* = null):Boolean; + AS3 function some(callback:*, receiver:Object = null):Boolean { + return _some(callback, receiver); + } AS3 native function sort(... rest):*; @@ -204,5 +206,7 @@ package { AS3 native function unshift(... rest):uint; public static const length:int = 1; + + private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorDouble.as b/core/src/avm2/globals/VectorDouble.as index 33cd76b68239..3d887c7e109c 100644 --- a/core/src/avm2/globals/VectorDouble.as +++ b/core/src/avm2/globals/VectorDouble.as @@ -182,7 +182,9 @@ package __AS3__.vec { AS3 native function slice(start:Number = 0, end:Number = 2147483647):Vector$double; - AS3 native function some(callback:*, receiver:Object = null):Boolean; + AS3 function some(callback:*, receiver:Object = null):Boolean { + return _some(callback, receiver); + } AS3 native function sort(func:*):Vector$double; @@ -197,5 +199,7 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; + + private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorInt.as b/core/src/avm2/globals/VectorInt.as index c9d6cbb3f9ca..4cfec2922af5 100644 --- a/core/src/avm2/globals/VectorInt.as +++ b/core/src/avm2/globals/VectorInt.as @@ -182,7 +182,9 @@ package __AS3__.vec { AS3 native function slice(start:Number = 0, end:Number = 2147483647):Vector$int; - AS3 native function some(callback:*, receiver:Object = null):Boolean; + AS3 function some(callback:*, receiver:Object = null):Boolean { + return _some(callback, receiver); + } AS3 native function sort(func:*):Vector$int; @@ -197,5 +199,7 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; + + private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorObject.as b/core/src/avm2/globals/VectorObject.as index 1c6187670fb3..6c392958e59e 100644 --- a/core/src/avm2/globals/VectorObject.as +++ b/core/src/avm2/globals/VectorObject.as @@ -184,7 +184,9 @@ package __AS3__.vec { AS3 native function slice(start:Number = 0, end:Number = 2147483647):Vector$object; - AS3 native function some(callback:*, receiver:Object = null):Boolean; + AS3 function some(callback:*, receiver:Object = null):Boolean { + return _some(callback, receiver); + } AS3 native function sort(func:*):Vector$object; @@ -216,5 +218,7 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; + + private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorUint.as b/core/src/avm2/globals/VectorUint.as index 4ac5a539e291..5b28dac1d6d8 100644 --- a/core/src/avm2/globals/VectorUint.as +++ b/core/src/avm2/globals/VectorUint.as @@ -183,7 +183,9 @@ package __AS3__.vec { AS3 native function slice(start:Number = 0, end:Number = 2147483647):Vector$uint; - AS3 native function some(callback:*, receiver:Object = null):Boolean; + AS3 function some(callback:*, receiver:Object = null):Boolean { + return _some(callback, receiver); + } AS3 native function sort(func:*):Vector$uint; @@ -198,5 +200,7 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; + + private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 39f34ac4b7a9..1bacf1d847b7 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -310,7 +310,10 @@ pub fn for_each<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; @@ -329,7 +332,10 @@ pub fn map<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, + }; let receiver = args.get_value(1); let mut new_array = ArrayStorage::new(0); let mut iter = ArrayIter::new(activation, this)?; @@ -351,7 +357,10 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, + }; let receiver = args.get_value(1); let mut new_array = ArrayStorage::new(0); let mut iter = ArrayIter::new(activation, this)?; @@ -377,7 +386,10 @@ pub fn every<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; @@ -394,15 +406,17 @@ pub fn every<'gc>( Ok(true.into()) } -/// Implements `Array.some` -pub fn some<'gc>( +pub fn _some<'gc>( activation: &mut Activation<'_, 'gc>, this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 5c79c4314df0..4873dc304959 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -262,60 +262,22 @@ pub fn join<'gc>( Ok(Value::Undefined) } -/// Implements `Vector.every` -pub fn every<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { - return Ok(false.into()); - }; - let receiver = args.get_value(1); - let mut iter = ArrayIter::new(activation, this)?; - - while let Some((i, item)) = iter.next(activation)? { - let result = callback - .call(activation, receiver, &[item, i.into(), this.into()])? - .coerce_to_boolean(); - - if !result { - return Ok(false.into()); +macro_rules! delegate_method_to_array { + ($name:ident, $method:path) => { + pub fn $name<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Value<'gc>, + args: &[Value<'gc>], + ) -> Result, Error<'gc>> { + $method(activation, this, args) } - } - - Ok(true.into()) -} - -/// Implements `Vector.some` -pub fn some<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { - return Ok(false.into()); }; - let receiver = args.get_value(1); - let mut iter = ArrayIter::new(activation, this)?; - - while let Some((i, item)) = iter.next(activation)? { - let result = callback - .call(activation, receiver, &[item, i.into(), this.into()])? - .coerce_to_boolean(); - - if result { - return Ok(true.into()); - } - } - - Ok(false.into()) } +delegate_method_to_array!(every, super::array::every); +delegate_method_to_array!(_some, super::array::_some); +delegate_method_to_array!(for_each, super::array::for_each); + /// Implements `Vector.filter` pub fn filter<'gc>( activation: &mut Activation<'_, 'gc>, @@ -324,8 +286,9 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { - return Ok(false.into()); + let callback = match args.get_value(0) { + Value::Null => return Ok(false.into()), + value => value, }; let receiver = args.get_value(1); @@ -349,27 +312,6 @@ pub fn filter<'gc>( Ok(VectorObject::from_vector(new_storage, activation)?.into()) } -/// Implements `Vector.forEach` -pub fn for_each<'gc>( - activation: &mut Activation<'_, 'gc>, - this: Value<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let this = this.as_object().unwrap(); - - let Some(callback) = args.get_value(0).coerce_to_function(activation)? else { - return Ok(false.into()); - }; - let receiver = args.get_value(1); - let mut iter = ArrayIter::new(activation, this)?; - - while let Some((i, item)) = iter.next(activation)? { - callback.call(activation, receiver, &[item, i.into(), this.into()])?; - } - - Ok(Value::Undefined) -} - /// Implements `Vector.indexOf` pub fn index_of<'gc>( activation: &mut Activation<'_, 'gc>, diff --git a/core/src/avm2/globals/vector_double.rs b/core/src/avm2/globals/vector_double.rs index 2f50477b247b..548072012f46 100644 --- a/core/src/avm2/globals/vector_double.rs +++ b/core/src/avm2/globals/vector_double.rs @@ -23,7 +23,7 @@ pub use crate::avm2::globals::vector::{ reverse, shift, slice, - some, + _some, sort, splice, unshift, diff --git a/core/src/avm2/globals/vector_int.rs b/core/src/avm2/globals/vector_int.rs index 6f9a1b6fa423..ea8fa839a892 100644 --- a/core/src/avm2/globals/vector_int.rs +++ b/core/src/avm2/globals/vector_int.rs @@ -23,7 +23,7 @@ pub use crate::avm2::globals::vector::{ reverse, shift, slice, - some, + _some, sort, splice, unshift, diff --git a/core/src/avm2/globals/vector_object.rs b/core/src/avm2/globals/vector_object.rs index 2cbce4867d90..2d6a77322b0c 100644 --- a/core/src/avm2/globals/vector_object.rs +++ b/core/src/avm2/globals/vector_object.rs @@ -23,7 +23,7 @@ pub use crate::avm2::globals::vector::{ reverse, shift, slice, - some, + _some, sort, splice, unshift, diff --git a/core/src/avm2/globals/vector_uint.rs b/core/src/avm2/globals/vector_uint.rs index a5c77dd18988..2a2924eb8e44 100644 --- a/core/src/avm2/globals/vector_uint.rs +++ b/core/src/avm2/globals/vector_uint.rs @@ -23,7 +23,7 @@ pub use crate::avm2::globals::vector::{ reverse, shift, slice, - some, + _some, sort, splice, unshift, diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index c770e609df7d..dcb6818b83ed 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -818,22 +818,6 @@ impl<'gc> Value<'gc> { }) } - pub fn coerce_to_function( - &self, - activation: &mut Activation<'_, 'gc>, - ) -> Result>, Error<'gc>> { - match self { - Value::Null | Value::Undefined => Ok(None), - _ => { - let function_class = activation.avm2().class_defs().function; - - let value = self.coerce_to_type(activation, function_class)?; - - Ok(Some(value)) - } - } - } - /// Coerce the value to a literal value / debug string. /// /// This matches the string formatting that appears to be in use in "debug" From 85fdbab0b3962a03042c68c0ddb26cfb4c6bd4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:00:24 +0200 Subject: [PATCH 06/13] remove nonsense --- core/src/avm2/globals/array.rs | 5 +---- core/src/avm2/globals/vector.rs | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 1bacf1d847b7..a035d2383ecb 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -310,10 +310,7 @@ pub fn for_each<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = match args.get_value(0) { - Value::Null => return Ok(false.into()), - value => value, - }; + let callback = args.get_value(0); let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 4873dc304959..b2ed129afc86 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -286,10 +286,7 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = match args.get_value(0) { - Value::Null => return Ok(false.into()), - value => value, - }; + let callback = args.get_value(0); let receiver = args.get_value(1); let value_type = this From 94adfcd72212ff659b510d2f6a9cff0aa97964e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:02:33 +0200 Subject: [PATCH 07/13] ref --- core/src/avm2/globals/array.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index a035d2383ecb..e2ae7f6237ef 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -329,10 +329,7 @@ pub fn map<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = match args.get_value(0) { - Value::Null => return Ok(false.into()), - value => value, - }; + let callback = args.get_value(0); let receiver = args.get_value(1); let mut new_array = ArrayStorage::new(0); let mut iter = ArrayIter::new(activation, this)?; From 17c586b04189f6b639c47210c83760d42202fec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:03:14 +0200 Subject: [PATCH 08/13] remove null check from array.filter --- core/src/avm2/globals/array.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index e2ae7f6237ef..5aadfaa09a16 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -351,10 +351,7 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = match args.get_value(0) { - Value::Null => return Ok(false.into()), - value => value, - }; + let callback = args.get_value(0); let receiver = args.get_value(1); let mut new_array = ArrayStorage::new(0); let mut iter = ArrayIter::new(activation, this)?; From 73d2d86e814443064362012bff1fef6ceec0edb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:11:28 +0200 Subject: [PATCH 09/13] simplify macro --- core/src/avm2/globals/vector.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index b2ed129afc86..9ddea7bd91a8 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -263,20 +263,20 @@ pub fn join<'gc>( } macro_rules! delegate_method_to_array { - ($name:ident, $method:path) => { - pub fn $name<'gc>( + ($method:ident) => { + pub fn $method<'gc>( activation: &mut Activation<'_, 'gc>, this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - $method(activation, this, args) + super::array::$method(activation, this, args) } }; } -delegate_method_to_array!(every, super::array::every); -delegate_method_to_array!(_some, super::array::_some); -delegate_method_to_array!(for_each, super::array::for_each); +delegate_method_to_array!(every); +delegate_method_to_array!(_some); +delegate_method_to_array!(for_each); /// Implements `Vector.filter` pub fn filter<'gc>( From 1abc68a1d11ba354ba80e9a62a521f5c1bd31681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:25:38 +0200 Subject: [PATCH 10/13] move methods --- core/src/avm2/globals/VectorDouble.as | 4 ++-- core/src/avm2/globals/VectorInt.as | 4 ++-- core/src/avm2/globals/VectorObject.as | 4 ++-- core/src/avm2/globals/VectorUint.as | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/avm2/globals/VectorDouble.as b/core/src/avm2/globals/VectorDouble.as index 3d887c7e109c..ff5bed81f72c 100644 --- a/core/src/avm2/globals/VectorDouble.as +++ b/core/src/avm2/globals/VectorDouble.as @@ -186,6 +186,8 @@ package __AS3__.vec { return _some(callback, receiver); } + private native function _some(callback:Function, receiver:Object):Boolean; + AS3 native function sort(func:*):Vector$double; AS3 native function splice(start:Number, deleteLen:Number, ... rest):Vector$double; @@ -199,7 +201,5 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; - - private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorInt.as b/core/src/avm2/globals/VectorInt.as index 4cfec2922af5..47025b881f39 100644 --- a/core/src/avm2/globals/VectorInt.as +++ b/core/src/avm2/globals/VectorInt.as @@ -186,6 +186,8 @@ package __AS3__.vec { return _some(callback, receiver); } + private native function _some(callback:Function, receiver:Object):Boolean; + AS3 native function sort(func:*):Vector$int; AS3 native function splice(start:Number, deleteLen:Number, ... rest):Vector$int; @@ -199,7 +201,5 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; - - private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorObject.as b/core/src/avm2/globals/VectorObject.as index 6c392958e59e..884ab98acadc 100644 --- a/core/src/avm2/globals/VectorObject.as +++ b/core/src/avm2/globals/VectorObject.as @@ -188,6 +188,8 @@ package __AS3__.vec { return _some(callback, receiver); } + private native function _some(callback:Function, receiver:Object):Boolean; + AS3 native function sort(func:*):Vector$object; AS3 native function splice(start:Number, deleteLen:Number, ... rest):Vector$object; @@ -218,7 +220,5 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; - - private native function _some(callback:Function, receiver:Object):Boolean; } } diff --git a/core/src/avm2/globals/VectorUint.as b/core/src/avm2/globals/VectorUint.as index 5b28dac1d6d8..2b5a6fc5d7f1 100644 --- a/core/src/avm2/globals/VectorUint.as +++ b/core/src/avm2/globals/VectorUint.as @@ -187,6 +187,8 @@ package __AS3__.vec { return _some(callback, receiver); } + private native function _some(callback:Function, receiver:Object):Boolean; + AS3 native function sort(func:*):Vector$uint; AS3 native function splice(start:Number, deleteLen:Number, ... rest):Vector$uint; @@ -200,7 +202,5 @@ package __AS3__.vec { } AS3 native function unshift(... rest):uint; - - private native function _some(callback:Function, receiver:Object):Boolean; } } From 92a395642e6a310d22b15a4d3e13a109c9d43083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Fri, 18 Jul 2025 21:42:57 +0200 Subject: [PATCH 11/13] refactor forEach and filter --- core/src/avm2/globals/array.rs | 10 ++++++++-- core/src/avm2/globals/vector.rs | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 5aadfaa09a16..3afb8b8fae78 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -310,7 +310,10 @@ pub fn for_each<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(Value::Undefined), + value => value, + }; let receiver = args.get_value(1); let mut iter = ArrayIter::new(activation, this)?; @@ -351,7 +354,10 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); + let callback = match args.get_value(0) { + Value::Null => return Ok(ArrayObject::empty(activation).into()), + value => value, + }; let receiver = args.get_value(1); let mut new_array = ArrayStorage::new(0); let mut iter = ArrayIter::new(activation, this)?; diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 9ddea7bd91a8..39f247ca8bb6 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -286,14 +286,19 @@ pub fn filter<'gc>( ) -> Result, Error<'gc>> { let this = this.as_object().unwrap(); - let callback = args.get_value(0); - let receiver = args.get_value(1); - let value_type = this .instance_class() .param() .expect("Receiver is parametrized vector"); // technically unreachable + let mut new_storage = VectorStorage::new(0, false, value_type, activation); + + let callback = match args.get_value(0) { + Value::Null => return Ok(VectorObject::from_vector(new_storage, activation)?.into()), + value => value, + }; + let receiver = args.get_value(1); + let mut iter = ArrayIter::new(activation, this)?; while let Some((i, item)) = iter.next(activation)? { From da08a6b0cbc640dc20f8600e4699eee7507522e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Sat, 19 Jul 2025 21:13:08 +0200 Subject: [PATCH 12/13] move _some definition --- core/src/avm2/globals/Array.as | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/globals/Array.as b/core/src/avm2/globals/Array.as index 2de461a527d1..e78af0735615 100644 --- a/core/src/avm2/globals/Array.as +++ b/core/src/avm2/globals/Array.as @@ -197,6 +197,8 @@ package { return _some(callback, receiver); } + private native function _some(callback:Function, receiver:Object):Boolean; + AS3 native function sort(... rest):*; AS3 native function sortOn(fieldNames:*, options:* = 0, ... rest):*; @@ -206,7 +208,5 @@ package { AS3 native function unshift(... rest):uint; public static const length:int = 1; - - private native function _some(callback:Function, receiver:Object):Boolean; } } From 22b2892f271319eead37eecd6e1ba30ad6a64590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Sat, 19 Jul 2025 21:29:14 +0200 Subject: [PATCH 13/13] fix every behaviour with null callback --- core/src/avm2/globals/array.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 3afb8b8fae78..81551c99a551 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -384,7 +384,7 @@ pub fn every<'gc>( let this = this.as_object().unwrap(); let callback = match args.get_value(0) { - Value::Null => return Ok(false.into()), + Value::Null => return Ok(true.into()), value => value, }; let receiver = args.get_value(1);