diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 8b3f745ddbe3..f99090b57b7b 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -891,12 +891,14 @@ impl<'a, 'gc> Activation<'a, 'gc> { MovieClipReference::try_from_stage_object(self, bc).unwrap(), ); let name = func.name(); - let prototype = Object::new(&self.context.strings, Some(self.prototypes().object)); - let func_obj = FunctionObject::function( + let prototype = Object::new( + &self.context.strings, + Some(self.prototypes().object), + ); + let func_obj = FunctionObject::bytecode(Gc::new(self.gc(), func)).build( &self.context.strings, - Gc::new(self.gc(), func), self.prototypes().function, - prototype, + Some(prototype), ); if let Some(name) = name { self.define_local(name, func_obj.into())?; diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 2ebd3a19dfad..bd71c50e34ee 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -14,7 +14,7 @@ use crate::string::{AvmString, StringContext, SwfStrExt as _}; use crate::tag_utils::SwfSlice; use gc_arena::{Collect, Gc, Mutation}; use ruffle_macros::istr; -use std::{borrow::Cow, fmt, num::NonZeroU8}; +use std::{borrow::Cow, num::NonZeroU8}; use swf::{avm1::types::FunctionFlags, SwfStr}; /// Represents a function defined in Ruffle's code. @@ -259,82 +259,10 @@ impl<'gc> Avm1Function<'gc> { *preload_r += 1; } } -} - -#[derive(Debug, Clone, Collect)] -#[collect(no_drop)] -struct Param<'gc> { - /// The register the argument will be preloaded into. - /// - /// If `register` is `None`, then this parameter will be stored in a named variable in the - /// function activation and can be accessed using `GetVariable`/`SetVariable`. - /// Otherwise, the parameter is loaded into a register and must be accessed with - /// `Push`/`StoreRegister`. - #[collect(require_static)] - register: Option, - - /// The name of the parameter. - name: AvmString<'gc>, -} - -/// Represents a function that can be defined in the Ruffle runtime or by the -/// AVM1 bytecode itself. -#[derive(Copy, Clone, Collect)] -#[collect(no_drop)] -pub enum Executable<'gc> { - /// A function provided by the Ruffle runtime and implemented in Rust. - Native(#[collect(require_static)] NativeFunction), - - /// ActionScript data defined by a previous `DefineFunction` or - /// `DefineFunction2` action. - Action(Gc<'gc, Avm1Function<'gc>>), -} - -impl fmt::Debug for Executable<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Executable::Native(nf) => f - .debug_tuple("Executable::Native") - .field(&format!("{nf:p}")) - .finish(), - Executable::Action(af) => f - .debug_tuple("Executable::Action") - .field(&Gc::as_ptr(*af)) - .finish(), - } - } -} - -/// Indicates the default name to use for this execution in debug builds. -pub enum ExecutionName<'gc> { - Static(&'static str), - Dynamic(AvmString<'gc>), -} -impl From<&'static str> for ExecutionName<'_> { - fn from(string: &'static str) -> Self { - ExecutionName::Static(string) - } -} - -impl<'gc> From> for ExecutionName<'gc> { - fn from(string: AvmString<'gc>) -> Self { - ExecutionName::Dynamic(string) - } -} - -impl<'gc> Executable<'gc> { - /// A dummy `Executable` that does nothing, and returns `undefined`. - const EMPTY: Self = Self::Native(|_, _, _| Ok(Value::Undefined)); - - /// Execute the given code. - /// - /// Execution is not guaranteed to have completed when this function - /// returns. If on-stack execution is possible, then this function returns - /// a return value you must push onto the stack. Otherwise, you must - /// create a new stack frame and execute the action data yourself. + /// Execute the given function bytecode. #[expect(clippy::too_many_arguments)] - pub fn exec( + fn exec( &self, name: ExecutionName<'gc>, activation: &mut Activation<'_, 'gc>, @@ -344,15 +272,6 @@ impl<'gc> Executable<'gc> { reason: ExecutionReason, callee: Object<'gc>, ) -> Result, Error<'gc>> { - let af = match self { - Executable::Native(nf) => { - // TODO: Change NativeFunction to accept `this: Value`. - let this = this.coerce_to_object(activation); - return nf(activation, this, args); - } - Executable::Action(af) => af, - }; - let this_obj = match this { Value::Object(obj) => Some(obj), _ => None, @@ -366,7 +285,7 @@ impl<'gc> Executable<'gc> { .unwrap_or(target); let base_clip = if is_closure || reason == ExecutionReason::Special { - af.base_clip + self.base_clip .resolve_reference(activation) .map(|(_, _, dobj)| dobj) .unwrap_or(this_do) @@ -378,7 +297,7 @@ impl<'gc> Executable<'gc> { // * Use the SWF version from the SWF that defined the function. // * Use the base clip from when the function was defined. // * Close over the scope from when the function was defined. - (af.swf_version(), af.scope()) + (self.swf_version(), self.scope()) } else { // Function calls in a v5 SWF are *not* closures, and will use the settings of // `this`, regardless of the function's origin: @@ -411,12 +330,12 @@ impl<'gc> Executable<'gc> { let arguments_caller = activation.callee; let name = if cfg!(feature = "avm_debug") { - Cow::Owned(af.debug_string_for_call(activation, name, args)) + Cow::Owned(self.debug_string_for_call(activation, name, args)) } else { Cow::Borrowed("[Anonymous]") }; - let is_this_inherited = af + let is_this_inherited = self .flags .intersects(FunctionFlags::PRELOAD_THIS | FunctionFlags::SUPPRESS_THIS); let local_this = if is_this_inherited { @@ -431,21 +350,21 @@ impl<'gc> Executable<'gc> { activation.id.function(name, reason, max_recursion_depth)?, swf_version, child_scope, - af.constant_pool, + self.constant_pool, base_clip, local_this, Some(callee), ); - frame.allocate_local_registers(af.register_count()); + frame.allocate_local_registers(self.register_count()); let mut preload_r = 1; - af.load_this(&mut frame, this, &mut preload_r); - af.load_arguments(&mut frame, args, arguments_caller, &mut preload_r); - af.load_super(&mut frame, this_obj, depth, &mut preload_r); - af.load_root(&mut frame, &mut preload_r); - af.load_parent(&mut frame, &mut preload_r); - af.load_global(&mut frame, &mut preload_r); + self.load_this(&mut frame, this, &mut preload_r); + self.load_arguments(&mut frame, args, arguments_caller, &mut preload_r); + self.load_super(&mut frame, this_obj, depth, &mut preload_r); + self.load_root(&mut frame, &mut preload_r); + self.load_parent(&mut frame, &mut preload_r); + self.load_global(&mut frame, &mut preload_r); // Any unassigned args are set to undefined to prevent assignments from leaking to the parent scope (#2166) let args_iter = args @@ -455,7 +374,7 @@ impl<'gc> Executable<'gc> { //TODO: What happens if the argument registers clash with the //preloaded registers? What gets done last? - for (param, value) in af.params.iter().zip(args_iter) { + for (param, value) in self.params.iter().zip(args_iter) { if let Some(register) = param.register { frame.set_local_register(register.get(), value); } else { @@ -463,23 +382,58 @@ impl<'gc> Executable<'gc> { } } - Ok(frame.run_actions(af.data.clone())?.value()) + Ok(frame.run_actions(self.data.clone())?.value()) } } -impl From for Executable<'_> { - fn from(nf: NativeFunction) -> Self { - Executable::Native(nf) +#[derive(Debug, Clone, Collect)] +#[collect(no_drop)] +struct Param<'gc> { + /// The register the argument will be preloaded into. + /// + /// If `register` is `None`, then this parameter will be stored in a named variable in the + /// function activation and can be accessed using `GetVariable`/`SetVariable`. + /// Otherwise, the parameter is loaded into a register and must be accessed with + /// `Push`/`StoreRegister`. + #[collect(require_static)] + register: Option, + + /// The name of the parameter. + name: AvmString<'gc>, +} + +/// Represents a function that can be defined in the Ruffle runtime or by the +/// AVM1 bytecode itself. +#[derive(Copy, Clone, Collect)] +#[collect(no_drop)] +enum Executable<'gc> { + /// A function provided by the Ruffle runtime and implemented in Rust. + Native(#[collect(require_static)] NativeFunction), + + /// ActionScript data defined by a previous `DefineFunction` or + /// `DefineFunction2` action. + Action(Gc<'gc, Avm1Function<'gc>>), +} + +/// Indicates the default name to use for this execution in debug builds. +pub enum ExecutionName<'gc> { + Static(&'static str), + Dynamic(AvmString<'gc>), +} + +impl From<&'static str> for ExecutionName<'_> { + fn from(string: &'static str) -> Self { + ExecutionName::Static(string) } } -impl<'gc> From>> for Executable<'gc> { - fn from(af: Gc<'gc, Avm1Function<'gc>>) -> Self { - Executable::Action(af) +impl<'gc> From> for ExecutionName<'gc> { + fn from(string: AvmString<'gc>) -> Self { + ExecutionName::Dynamic(string) } } -#[derive(Collect)] +#[derive(Copy, Clone, Collect)] #[collect(no_drop)] pub struct FunctionObject<'gc> { /// The code that will be invoked when this object is called. @@ -492,91 +446,66 @@ pub struct FunctionObject<'gc> { } impl<'gc> FunctionObject<'gc> { - /// Construct a function sans prototype. - pub fn bare_function( - context: &StringContext<'gc>, - function: Executable<'gc>, - constructor: Option, - fn_proto: Object<'gc>, - ) -> Object<'gc> { - let obj = Object::new(context, Some(fn_proto)); - let native = NativeObject::Function(Gc::new( - context.gc(), - Self { - function, - constructor, - }, - )); - obj.set_native(context.gc(), native); - obj - } - - /// Construct a function with any combination of regular and constructor parts. - /// - /// Since prototypes need to link back to themselves, this function builds - /// both objects itself and returns the function to you, fully allocated. + /// Builds a new function object. /// /// `fn_proto` refers to the implicit proto of the function object, and the - /// `prototype` refers to the explicit prototype of the function. - /// The function and its prototype will be linked to each other. - fn allocate_function( + /// `prototype` refers to the explicit prototype of the function. If the + /// prototype is present, it will be linked with the newly-created object. + pub fn build( + self, context: &StringContext<'gc>, - function: Executable<'gc>, - constructor: Option, fn_proto: Object<'gc>, - prototype: Object<'gc>, + prototype: Option>, ) -> Object<'gc> { - let function = Self::bare_function(context, function, constructor, fn_proto); + let obj = Object::new(context, Some(fn_proto)); + let native = NativeObject::Function(Gc::new(context.gc(), self)); + obj.set_native(context.gc(), native); - prototype.define_value( - context.gc(), - istr!(context, "constructor"), - Value::Object(function), - Attribute::DONT_ENUM, - ); - function.define_value( - context.gc(), - istr!(context, "prototype"), - prototype.into(), - Attribute::empty(), - ); + if let Some(prototype) = prototype { + prototype.define_value( + context.gc(), + istr!(context, "constructor"), + Value::Object(obj), + Attribute::DONT_ENUM, + ); + obj.define_value( + context.gc(), + istr!(context, "prototype"), + prototype.into(), + Attribute::empty(), + ); + } - function + obj } - /// Constructs a function that does nothing. + /// A function that does nothing. /// /// This can also serve as a no-op constructor. - pub fn empty( - context: &StringContext<'gc>, - fn_proto: Object<'gc>, - prototype: Object<'gc>, - ) -> Object<'gc> { - Self::allocate_function(context, Executable::EMPTY, None, fn_proto, prototype) + pub fn empty() -> Self { + Self { + function: Executable::Native(|_, _, _| Ok(Value::Undefined)), + constructor: None, + } } - /// Construct a function from AVM1 bytecode and associated protos. - pub fn function( - context: &StringContext<'gc>, - function: Gc<'gc, Avm1Function<'gc>>, - fn_proto: Object<'gc>, - prototype: Object<'gc>, - ) -> Object<'gc> { - Self::allocate_function(context, function.into(), None, fn_proto, prototype) + /// A function with AVM1 bytecode. + pub fn bytecode(function: Gc<'gc, Avm1Function<'gc>>) -> Self { + Self { + function: Executable::Action(function), + constructor: None, + } } - /// Construct a function from a native executable and associated protos. - pub fn native( - context: &StringContext<'gc>, - function: NativeFunction, - fn_proto: Object<'gc>, - prototype: Object<'gc>, - ) -> Object<'gc> { - let function = Executable::Native(function); - Self::allocate_function(context, function, None, fn_proto, prototype) + /// A function with a native executable. + pub fn native(function: NativeFunction) -> Self { + Self { + function: Executable::Native(function), + constructor: None, + } } - /// Construct a native constructor from native executables and associated protos. + /// A native constructor. /// /// This differs from [`Self::native`] in two important ways: /// - When called through `new`, the return value will always become the result of the @@ -584,47 +513,74 @@ impl<'gc> FunctionObject<'gc> { /// if the object was successfully constructed, or `undefined` if not. /// - When called as a normal function, `function` will be called instead of `constructor`; /// if it is `None`, the return value will be `undefined`. - pub fn constructor( - context: &StringContext<'gc>, - constructor: NativeFunction, - function: Option, - fn_proto: Object<'gc>, - prototype: Object<'gc>, - ) -> Object<'gc> { - Self::allocate_function( - context, - Executable::Native(function.unwrap_or(|_, _, _| Ok(Value::Undefined))), - Some(constructor), - fn_proto, - prototype, - ) - } - - pub fn as_executable(&self) -> Executable<'gc> { - self.function + pub fn constructor(constructor: NativeFunction, function: Option) -> Self { + Self { + function: Executable::Native(function.unwrap_or(|_, _, _| Ok(Value::Undefined))), + constructor: Some(constructor), + } } - pub fn as_constructor(&self) -> Executable<'gc> { - if let Some(constr) = self.constructor { - Executable::Native(constr) - } else { - self.function + /// Execute the given code. + /// + /// This is fairly low-level; prefer using other call methods if possible. + #[expect(clippy::too_many_arguments)] + pub fn exec( + self, + name: ExecutionName<'gc>, + activation: &mut Activation<'_, 'gc>, + this: Value<'gc>, + depth: u8, + args: &[Value<'gc>], + reason: ExecutionReason, + callee: Object<'gc>, + ) -> Result, Error<'gc>> { + match self.function { + Executable::Native(nf) => { + // TODO: Change NativeFunction to accept `this: Value`. + let this = this.coerce_to_object(activation); + nf(activation, this, args) + } + Executable::Action(af) => af.exec(name, activation, this, depth, args, reason, callee), } } - pub fn is_native_constructor(&self) -> bool { - self.constructor.is_some() + /// Execute the given code as a constructor. + /// + /// This is fairly low-level; prefer using other call methods if possible. + #[expect(clippy::too_many_arguments)] + pub fn exec_constructor( + self, + name: ExecutionName<'gc>, + activation: &mut Activation<'_, 'gc>, + this: Value<'gc>, + depth: u8, + args: &[Value<'gc>], + reason: ExecutionReason, + callee: Object<'gc>, + ) -> Result, Error<'gc>> { + let constr = match self.constructor { + Some(constr) => Executable::Native(constr), + None => self.function, + }; + match constr { + Executable::Native(nf) => { + // TODO: Change NativeFunction to accept `this: Value`. + let this = this.coerce_to_object(activation); + nf(activation, this, args) + } + Executable::Action(af) => af.exec(name, activation, this, depth, args, reason, callee), + } } pub fn call( - &self, + self, name: impl Into>, activation: &mut Activation<'_, 'gc>, callee: Object<'gc>, this: Value<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - self.function.exec( + self.exec( name.into(), activation, this, @@ -636,30 +592,16 @@ impl<'gc> FunctionObject<'gc> { } pub fn construct_on_existing( - &self, + self, activation: &mut Activation<'_, 'gc>, callee: Object<'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result<(), Error<'gc>> { - // TODO: de-duplicate code. - this.define_value( - activation.gc(), - istr!("__constructor__"), - callee.into(), - Attribute::DONT_ENUM, - ); - if activation.swf_version() < 7 { - this.define_value( - activation.gc(), - istr!("constructor"), - callee.into(), - Attribute::DONT_ENUM, - ); - } + Self::define_constructor_props(activation, this, callee.into()); // Always ignore the constructor's return value. - let _ = self.as_constructor().exec( + let _ = self.exec_constructor( ExecutionName::Static("[ctor]"), activation, this.into(), @@ -673,7 +615,7 @@ impl<'gc> FunctionObject<'gc> { } pub fn construct( - &self, + self, activation: &mut Activation<'_, 'gc>, callee: Object<'gc>, args: &[Value<'gc>], @@ -683,37 +625,40 @@ impl<'gc> FunctionObject<'gc> { .coerce_to_object(activation); let this = Object::new(activation.strings(), Some(prototype)); - // TODO: de-duplicate code. + Self::define_constructor_props(activation, this, callee.into()); + + // Propagate the return value only for native constructors. + let propagate = self.constructor.is_some(); + let ret = self.exec_constructor( + ExecutionName::Static("[ctor]"), + activation, + this.into(), + 1, + args, + ExecutionReason::FunctionCall, + callee, + )?; + Ok(if propagate { ret } else { this.into() }) + } + + fn define_constructor_props( + activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, + callee: Value<'gc>, + ) { this.define_value( activation.gc(), istr!("__constructor__"), - callee.into(), + callee, Attribute::DONT_ENUM, ); if activation.swf_version() < 7 { this.define_value( activation.gc(), istr!("constructor"), - callee.into(), + callee, Attribute::DONT_ENUM, ); } - - let result = self.as_constructor().exec( - ExecutionName::Static("[ctor]"), - activation, - this.into(), - 1, - args, - ExecutionReason::FunctionCall, - callee, - )?; - - if self.is_native_constructor() { - // Propagate the native method's return value. - Ok(result) - } else { - Ok(this.into()) - } } } diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 204714a60474..d6655fb2123a 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -297,7 +297,7 @@ pub fn create_timer<'gc>( use crate::timer::TimerCallback; let (callback, interval) = match args.get(0) { - Some(Value::Object(o)) if o.as_executable().is_some() => ( + Some(Value::Object(o)) if o.as_function().is_some() => ( TimerCallback::Avm1Function { func: *o, params: args.get(2..).unwrap_or_default().to_vec(), diff --git a/core/src/avm1/globals/bevel_filter.rs b/core/src/avm1/globals/bevel_filter.rs index 987d6882e3fd..0d935fce122b 100644 --- a/core/src/avm1/globals/bevel_filter.rs +++ b/core/src/avm1/globals/bevel_filter.rs @@ -450,9 +450,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::BevelFilter(bevel_filter) => bevel_filter, - _ => return Ok(Value::Undefined), + let NativeObject::BevelFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/blur_filter.rs b/core/src/avm1/globals/blur_filter.rs index d7f1b659ee42..0bc7cc2dcd89 100644 --- a/core/src/avm1/globals/blur_filter.rs +++ b/core/src/avm1/globals/blur_filter.rs @@ -160,9 +160,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::BlurFilter(blur_filter) => blur_filter, - _ => return Ok(Value::Undefined), + let NativeObject::BlurFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/color_matrix_filter.rs b/core/src/avm1/globals/color_matrix_filter.rs index c6c3bb4726a8..0cd364b07025 100644 --- a/core/src/avm1/globals/color_matrix_filter.rs +++ b/core/src/avm1/globals/color_matrix_filter.rs @@ -145,9 +145,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::ColorMatrixFilter(color_matrix_filter) => color_matrix_filter, - _ => return Ok(Value::Undefined), + let NativeObject::ColorMatrixFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/convolution_filter.rs b/core/src/avm1/globals/convolution_filter.rs index 160ddce5bea3..c3d20de937ce 100644 --- a/core/src/avm1/globals/convolution_filter.rs +++ b/core/src/avm1/globals/convolution_filter.rs @@ -357,9 +357,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::ConvolutionFilter(convolution_filter) => convolution_filter, - _ => return Ok(Value::Undefined), + let NativeObject::ConvolutionFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/date.rs b/core/src/avm1/globals/date.rs index 8c418879f86b..c8135c5a7b27 100644 --- a/core/src/avm1/globals/date.rs +++ b/core/src/avm1/globals/date.rs @@ -454,9 +454,8 @@ fn method<'gc>( _ => {} } - let date_ref = match this.native() { - NativeObject::Date(date) => date, - _ => return Ok(Value::Undefined), + let NativeObject::Date(date_ref) = this.native() else { + return Ok(Value::Undefined); }; let date = date_ref.get(); diff --git a/core/src/avm1/globals/displacement_map_filter.rs b/core/src/avm1/globals/displacement_map_filter.rs index 7e640a6612a0..124a567ebb23 100644 --- a/core/src/avm1/globals/displacement_map_filter.rs +++ b/core/src/avm1/globals/displacement_map_filter.rs @@ -346,9 +346,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::DisplacementMapFilter(displacement_map_filter) => displacement_map_filter, - _ => return Ok(Value::Undefined), + let NativeObject::DisplacementMapFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/drop_shadow_filter.rs b/core/src/avm1/globals/drop_shadow_filter.rs index eba3d859469d..8712f28ea0aa 100644 --- a/core/src/avm1/globals/drop_shadow_filter.rs +++ b/core/src/avm1/globals/drop_shadow_filter.rs @@ -373,9 +373,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::DropShadowFilter(drop_shadow_filter) => drop_shadow_filter, - _ => return Ok(Value::Undefined), + let NativeObject::DropShadowFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/function.rs b/core/src/avm1/globals/function.rs index 810511fcf11e..cd968752f554 100644 --- a/core/src/avm1/globals/function.rs +++ b/core/src/avm1/globals/function.rs @@ -62,7 +62,7 @@ pub fn call<'gc>( }; // NOTE: does not use `Object::call`, as `super()` only works with direct calls. - match func.as_executable() { + match func.as_function() { Some(exec) => exec.exec( ExecutionName::Static("[Anonymous]"), activation, @@ -103,7 +103,7 @@ pub fn apply<'gc>( } // NOTE: does not use `Object::call`, as `super()` only works with direct calls. - match func.as_executable() { + match func.as_function() { Some(exec) => exec.exec( ExecutionName::Static("[Anonymous]"), activation, diff --git a/core/src/avm1/globals/glow_filter.rs b/core/src/avm1/globals/glow_filter.rs index b54dd5cbb6cb..2a41fe778df8 100644 --- a/core/src/avm1/globals/glow_filter.rs +++ b/core/src/avm1/globals/glow_filter.rs @@ -295,9 +295,8 @@ fn method<'gc>( return Ok(this.into()); } - let this = match this.native() { - NativeObject::GlowFilter(glow_filter) => glow_filter, - _ => return Ok(Value::Undefined), + let NativeObject::GlowFilter(this) = this.native() else { + return Ok(Value::Undefined); }; Ok(match index { diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index a1cd2efb3bfc..41981189879a 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -124,7 +124,7 @@ fn to_string<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - if this.as_executable().is_some() { + if this.as_function().is_some() { Ok(AvmString::new_ascii_static(activation.gc(), b"[type Function]").into()) } else { Ok(AvmString::new_ascii_static(activation.gc(), b"[object Object]").into()) @@ -183,7 +183,7 @@ pub fn register_class<'gc>( let constructor = match constructor { Value::Null | Value::Undefined => None, - Value::Object(obj) if obj.as_executable().is_some() => Some(*obj), + Value::Object(obj) if obj.as_function().is_some() => Some(*obj), _ => return Ok(false.into()), }; @@ -212,7 +212,7 @@ fn watch<'gc>( .get(1) .unwrap_or(&Value::Undefined) .coerce_to_object(activation); - if callback.as_executable().is_none() { + if callback.as_function().is_none() { return Ok(false.into()); } let user_data = args.get(2).cloned().unwrap_or(Value::Undefined); diff --git a/core/src/avm1/globals/shared_object.rs b/core/src/avm1/globals/shared_object.rs index 730f6d8517fe..4642fbc68e31 100644 --- a/core/src/avm1/globals/shared_object.rs +++ b/core/src/avm1/globals/shared_object.rs @@ -104,7 +104,7 @@ fn recursive_serialize<'gc>( match elem { Value::Object(o) => { - if o.as_executable().is_some() { + if o.as_function().is_some() { } else if o.as_display_object().is_some() { writer.undefined(name.as_ref()) } else if let NativeObject::Array(_) = o.native() { diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index 6ddeea802a45..a09ad51be0cb 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -230,7 +230,7 @@ impl<'gc> Object<'gc> { while let Value::Object(this_proto) = proto { if this_proto.has_own_virtual(activation, name) { if let Some(setter) = this_proto.setter(name, activation) { - if let Some(exec) = setter.as_executable() { + if let Some(exec) = setter.as_function() { let _ = exec.exec( ExecutionName::Static("[Setter]"), activation, @@ -289,7 +289,7 @@ impl<'gc> Object<'gc> { // the method was found on the object's prototype. let depth = depth.max(1); - match method.as_executable() { + match method.as_function() { Some(exec) => exec.exec( ExecutionName::Dynamic(name), activation, @@ -405,7 +405,7 @@ pub fn search_prototype<'gc>( } if let Some(getter) = p.getter(name, activation) { - if let Some(exec) = getter.as_executable() { + if let Some(exec) = getter.as_function() { let result = exec.exec( ExecutionName::Static("[Getter]"), activation, diff --git a/core/src/avm1/object/script_object.rs b/core/src/avm1/object/script_object.rs index 50466d411f0f..c3298dcc6aa0 100644 --- a/core/src/avm1/object/script_object.rs +++ b/core/src/avm1/object/script_object.rs @@ -1,6 +1,6 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; -use crate::avm1::function::{Executable, ExecutionName, ExecutionReason}; +use crate::avm1::function::{ExecutionName, ExecutionReason, FunctionObject}; use crate::avm1::object::{stage_object, NativeObject}; use crate::avm1::property::{Attribute, Property}; use crate::avm1::property_map::{Entry, PropertyMap}; @@ -38,7 +38,7 @@ impl<'gc> Watcher<'gc> { this: Object<'gc>, ) -> Result, Error<'gc>> { let args = [Value::String(name), old_value, new_value, self.user_data]; - let exec = self.callback.as_executable().unwrap(); + let exec = self.callback.as_function().unwrap(); exec.exec( ExecutionName::Dynamic(name), activation, @@ -288,7 +288,7 @@ impl<'gc> Object<'gc> { }; if let Some(setter) = setter { - if let Some(exec) = setter.as_executable() { + if let Some(exec) = setter.as_function() { if let Err(Error::ThrownValue(e)) = exec.exec( ExecutionName::Static("[Setter]"), activation, @@ -831,11 +831,11 @@ impl<'gc> Object<'gc> { } } - /// Get the underlying executable for this object, if it exists. - pub fn as_executable(self) -> Option> { - // Even though `super` calls the class constructor, it doesn't count as an executable. + /// Get the underlying function for this object, if it exists. + pub fn as_function(self) -> Option> { + // Even though `super` calls the class constructor, it doesn't count as a function. if let NativeObject::Function(func) = self.native_no_super() { - Some(func.as_executable()) + Some(*func) } else { None } diff --git a/core/src/avm1/object/super_object.rs b/core/src/avm1/object/super_object.rs index 73389fa730a1..e2dbe0fb4854 100644 --- a/core/src/avm1/object/super_object.rs +++ b/core/src/avm1/object/super_object.rs @@ -83,7 +83,7 @@ impl<'gc> SuperObject<'gc> { return Ok(Value::Undefined); }; - constr.as_constructor().exec( + constr.exec_constructor( name.into(), activation, self.this().into(), @@ -108,7 +108,7 @@ impl<'gc> SuperObject<'gc> { _ => return Ok(Value::Undefined), }; - match method.as_executable() { + match method.as_function() { Some(exec) => exec.exec( ExecutionName::Dynamic(name), activation, diff --git a/core/src/avm1/property_decl.rs b/core/src/avm1/property_decl.rs index c42b21bfef24..a379ba24fffe 100644 --- a/core/src/avm1/property_decl.rs +++ b/core/src/avm1/property_decl.rs @@ -2,7 +2,7 @@ use gc_arena::Mutation; -use crate::avm1::function::{Executable, FunctionObject, NativeFunction}; +use crate::avm1::function::{FunctionObject, NativeFunction}; use crate::avm1::property::Attribute; use crate::avm1::{Object, Value}; use crate::string::{HasStringContext, StringContext, WStr}; @@ -33,7 +33,7 @@ impl<'gc> DeclContext<'_, 'gc> { pub fn empty_class(&self, super_proto: Object<'gc>) -> SystemClass<'gc> { let proto = Object::new(self.strings, Some(super_proto)); - let constr = FunctionObject::empty(self.strings, self.fn_proto, proto); + let constr = FunctionObject::empty().build(self.strings, self.fn_proto, Some(proto)); SystemClass { proto, constr } } @@ -41,7 +41,8 @@ impl<'gc> DeclContext<'_, 'gc> { /// is implemented in bytecode in Flash Player's `playerglobals.swf`. pub fn class(&self, function: NativeFunction, super_proto: Object<'gc>) -> SystemClass<'gc> { let proto = Object::new(self.strings, Some(super_proto)); - let constr = FunctionObject::native(self.strings, function, self.fn_proto, proto); + let constr = + FunctionObject::native(function).build(self.strings, self.fn_proto, Some(proto)); SystemClass { proto, constr } } @@ -63,8 +64,11 @@ impl<'gc> DeclContext<'_, 'gc> { function: Option, proto: Object<'gc>, ) -> SystemClass<'gc> { - let constr = - FunctionObject::constructor(self.strings, constructor, function, self.fn_proto, proto); + let constr = FunctionObject::constructor(constructor, function).build( + self.strings, + self.fn_proto, + Some(proto), + ); SystemClass { proto, constr } } } @@ -127,18 +131,16 @@ impl Declaration { let name = context.intern_static(WStr::from_units(self.name)); let value = match self.kind { DeclKind::Property { getter, setter } => { - let getter = FunctionObject::native(context, getter, fn_proto, fn_proto); + // Property objects are unobservable by user code, so a bare function is enough. + let getter = FunctionObject::native(getter).build(context, fn_proto, None); let setter = setter - .map(|setter| FunctionObject::native(context, setter, fn_proto, fn_proto)); + .map(|setter| FunctionObject::native(setter).build(context, fn_proto, None)); this.add_property(mc, name.into(), getter, setter, self.attributes); return Value::Undefined; } - DeclKind::Method(func) => { - FunctionObject::bare_function(context, Executable::Native(func), None, fn_proto) - .into() - } - DeclKind::Function(func) => { - FunctionObject::native(context, func, fn_proto, fn_proto).into() + DeclKind::Method(f) | DeclKind::Function(f) => { + let p = matches!(self.kind, DeclKind::Function(_)).then_some(fn_proto); + FunctionObject::native(f).build(context, fn_proto, p).into() } DeclKind::String(s) => context.intern_static(WStr::from_units(s)).into(), DeclKind::Bool(b) => b.into(), diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index cb972c5b59a3..a6922b86823c 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -411,7 +411,7 @@ impl<'gc> Value<'gc> { )? { Value::String(s) => s, _ => { - if object.as_executable().is_some() { + if object.as_function().is_some() { AvmString::new_ascii_static(activation.gc(), b"[type Function]") } else { AvmString::new_ascii_static(activation.gc(), b"[type Object]") @@ -454,7 +454,7 @@ impl<'gc> Value<'gc> { Value::Number(_) => istr!("number"), Value::Bool(_) => istr!("boolean"), Value::String(_) => istr!("string"), - Value::Object(object) if object.as_executable().is_some() => istr!("function"), + Value::Object(object) if object.as_function().is_some() => istr!("function"), Value::MovieClip(_) => istr!("movieclip"), Value::Object(_) => istr!("object"), } @@ -891,7 +891,6 @@ fn string_to_f64(mut s: &WStr, swf_version: u8) -> f64 { #[cfg(test)] #[expect(clippy::unreadable_literal)] // Large numeric literals in tests mod test { - use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::function::FunctionObject; use crate::avm1::globals::create_globals; @@ -923,19 +922,10 @@ mod test { assert_eq!(vglobal.to_primitive_num(activation).unwrap(), undefined); - fn value_of_impl<'gc>( - _activation: &mut Activation<'_, 'gc>, - _: Object<'gc>, - _: &[Value<'gc>], - ) -> Result, Error<'gc>> { - Ok(5.into()) - } - - let valueof = FunctionObject::native( + let valueof = FunctionObject::native(|_, _, _| Ok(5.into())).build( &activation.context.strings, - value_of_impl, - protos.function, protos.function, + Some(protos.function), ); let o = Object::new(&activation.context.strings, Some(protos.object)); diff --git a/core/src/debug_ui/avm1.rs b/core/src/debug_ui/avm1.rs index 4275b6aec5b6..76bcef4a9e08 100644 --- a/core/src/debug_ui/avm1.rs +++ b/core/src/debug_ui/avm1.rs @@ -145,7 +145,7 @@ impl Avm1ObjectWindow { }; } Value::Object(value) => { - if value.as_executable().is_some() { + if value.as_function().is_some() { ui.label("Function"); } else if ui.button(object_name(value)).clicked() { messages.push(Message::TrackAVM1Object(AVM1ObjectHandle::new( @@ -357,7 +357,7 @@ impl UiExt for egui::Ui { fn object_name(object: Object) -> String { // TODO: Find a way to give more meaningful names here. // Matching __proto__ to a constant and taking the constants name works, but is super expensive - if object.as_executable().is_some() { + if object.as_function().is_some() { format!("Function {:p}", object.as_ptr()) } else if let NativeObject::Array(_) = object.native() { format!("Array {:p}", object.as_ptr())