diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 4dd38121d..bafa6dcb8 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -4,7 +4,6 @@ use std::rc::Rc; use crate::avm2::class::AllocatorFn; use crate::avm2::error::make_error_1107; -use crate::avm2::function::Executable; use crate::avm2::globals::SystemClasses; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::scope::ScopeChain; @@ -598,8 +597,13 @@ impl<'gc> Avm2<'gc> { } /// Pushes an executable on the call stack - pub fn push_call(&self, mc: &Mutation<'gc>, calling: &Executable<'gc>) { - self.call_stack.write(mc).push(calling) + pub fn push_call( + &self, + mc: &Mutation<'gc>, + method: Method<'gc>, + superclass: Option>, + ) { + self.call_stack.write(mc).push(method, superclass) } /// Pushes script initializer (global init) on the call stack diff --git a/core/src/avm2/call_stack.rs b/core/src/avm2/call_stack.rs index 088236274..9e09809ed 100644 --- a/core/src/avm2/call_stack.rs +++ b/core/src/avm2/call_stack.rs @@ -1,4 +1,4 @@ -use crate::avm2::function::{display_function, Executable}; +use crate::avm2::function::display_function; use crate::avm2::method::Method; use crate::avm2::object::ClassObject; use crate::string::WString; @@ -27,11 +27,8 @@ impl<'gc> CallStack<'gc> { Self { stack: Vec::new() } } - pub fn push(&mut self, exec: &Executable<'gc>) { - self.stack.push(CallNode::Method { - method: exec.as_method(), - superclass: exec.bound_superclass(), - }) + pub fn push(&mut self, method: Method<'gc>, superclass: Option>) { + self.stack.push(CallNode::Method { method, superclass }) } pub fn push_global_init(&mut self, script: Script<'gc>) { diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 01b935bfc..2a32489e5 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -1,7 +1,5 @@ -//! AVM2 executables. - use crate::avm2::activation::Activation; -use crate::avm2::method::{BytecodeMethod, Method, NativeMethod, ParamConfig}; +use crate::avm2::method::{Method, ParamConfig}; use crate::avm2::object::{ClassObject, Object}; use crate::avm2::scope::ScopeChain; use crate::avm2::traits::TraitKind; @@ -11,13 +9,12 @@ use crate::string::WString; use gc_arena::{Collect, Gc}; use std::fmt; -/// Represents code written in AVM2 bytecode that can be executed by some -/// means. +/// Represents a bound method. #[derive(Clone, Collect)] #[collect(no_drop)] -pub struct BytecodeExecutable<'gc> { +pub struct BoundMethod<'gc> { /// The method code to execute from a given ABC file. - method: Gc<'gc, BytecodeMethod<'gc>>, + method: Method<'gc>, /// The scope this method was defined in. scope: ScopeChain<'gc>, @@ -26,206 +23,66 @@ pub struct BytecodeExecutable<'gc> { /// /// If `None`, then the receiver provided by the caller is used. A /// `Some` value indicates a bound executable. - receiver: Option>, - - /// The bound superclass for this method. - /// - /// The `superclass` is the class that defined this method. If `None`, - /// then there is no defining superclass and `super` operations should fall - /// back to the `receiver`. - bound_superclass: Option>, -} - -#[derive(Clone, Collect)] -#[collect(no_drop)] -pub struct NativeExecutable<'gc> { - /// The method associated with the executable. - method: Gc<'gc, NativeMethod<'gc>>, - - /// The scope this method was defined in. - scope: ScopeChain<'gc>, - - /// The bound receiver for this method. bound_receiver: Option>, - /// The bound superclass for this method. + /// The bound class for this method. /// - /// The `superclass` is the class that defined this method. If `None`, - /// then there is no defining superclass and `super` operations should fall + /// The `class` is the class that defined this method. If `None`, + /// then there is no defining class and `super` operations should fall /// back to the `receiver`. - bound_superclass: Option>, + bound_class: Option>, } -/// Represents code that can be executed by some means. -#[derive(Clone, Collect)] -#[collect(no_drop)] -pub enum Executable<'gc> { - /// Code defined in Ruffle's binary. - Native(NativeExecutable<'gc>), - - /// Code defined in a loaded ABC file. - Action(BytecodeExecutable<'gc>), -} - -impl<'gc> Executable<'gc> { - /// Convert a method into an executable. +impl<'gc> BoundMethod<'gc> { pub fn from_method( method: Method<'gc>, scope: ScopeChain<'gc>, receiver: Option>, superclass: Option>, ) -> Self { - match method { - Method::Native(method) => Self::Native(NativeExecutable { - method, - scope, - bound_receiver: receiver, - bound_superclass: superclass, - }), - Method::Bytecode(method) => Self::Action(BytecodeExecutable { - method, - scope, - receiver, - bound_superclass: superclass, - }), + Self { + method, + scope, + bound_receiver: receiver, + bound_class: superclass, } } - /// Execute a method. - /// - /// The function will either be called directly if it is a Rust builtin, or - /// executed on the same AVM2 instance as the activation passed in here. - /// The value returned in either case will be provided here. - /// - /// It is a panicking logic error to attempt to execute user code while any - /// reachable object is currently under a GcCell write lock. - /// - /// Passed-in arguments will be conformed to the set of method parameters - /// declared on the function. pub fn exec( &self, unbound_receiver: Value<'gc>, - mut arguments: &[Value<'gc>], + arguments: &[Value<'gc>], activation: &mut Activation<'_, 'gc>, callee: Object<'gc>, ) -> Result, Error<'gc>> { - let ret = match self { - Executable::Native(bm) => { - let method = bm.method.method; - - let receiver = if let Some(receiver) = bm.bound_receiver { - receiver - } else if matches!(unbound_receiver, Value::Null | Value::Undefined) { - bm.scope - .get(0) - .expect("No global scope for function call") - .values() - } else { - unbound_receiver.coerce_to_object(activation)? - }; - - let caller_domain = activation.caller_domain(); - let caller_movie = activation.caller_movie(); - let subclass_object = bm.bound_superclass; - let mut activation = Activation::from_builtin( - activation.context.reborrow(), - subclass_object, - bm.scope, - caller_domain, - caller_movie, - ); - - if arguments.len() > bm.method.signature.len() && !bm.method.is_variadic { - return Err(format!( - "Attempted to call {:?} with {} arguments (more than {} is prohibited)", - bm.method.name, - arguments.len(), - bm.method.signature.len() - ) - .into()); - } - - if bm.method.resolved_signature.read().is_none() { - bm.method.resolve_signature(&mut activation)?; - } - - let resolved_signature = bm.method.resolved_signature.read(); - let resolved_signature = resolved_signature.as_ref().unwrap(); - - let arguments = activation.resolve_parameters( - Method::Native(bm.method), - arguments, - resolved_signature, - Some(callee), - )?; - activation - .context - .avm2 - .push_call(activation.context.gc_context, self); - method(&mut activation, receiver, &arguments) - } - Executable::Action(bm) => { - if bm.method.is_unchecked() { - let max_args = bm.method.signature().len(); - if arguments.len() > max_args && !bm.method.is_variadic() { - arguments = &arguments[..max_args]; - } - } - - let receiver = if let Some(receiver) = bm.receiver { - receiver - } else if matches!(unbound_receiver, Value::Null | Value::Undefined) { - bm.scope - .get(0) - .expect("No global scope for function call") - .values() - } else { - unbound_receiver.coerce_to_object(activation)? - }; - - let subclass_object = bm.bound_superclass; - - // This used to be a one step called Activation::from_method, - // but avoiding moving an Activation around helps perf - let mut activation = Activation::from_nothing(activation.context.reborrow()); - activation.init_from_method( - bm.method, - bm.scope, - receiver, - arguments, - subclass_object, - callee, - )?; - activation - .context - .avm2 - .push_call(activation.context.gc_context, self); - activation.run_actions(bm.method) - } + let receiver = if let Some(receiver) = self.bound_receiver { + receiver + } else if matches!(unbound_receiver, Value::Null | Value::Undefined) { + self.scope + .get(0) + .expect("No global scope for function call") + .values() + } else { + unbound_receiver.coerce_to_object(activation)? }; - activation - .context - .avm2 - .pop_call(activation.context.gc_context); - ret + + exec( + self.method, + self.scope, + receiver, + self.bound_class, + arguments, + activation, + callee, + ) } pub fn bound_superclass(&self) -> Option> { - match self { - Executable::Native(NativeExecutable { - bound_superclass, .. - }) => *bound_superclass, - Executable::Action(BytecodeExecutable { - bound_superclass, .. - }) => *bound_superclass, - } + self.bound_class } pub fn as_method(&self) -> Method<'gc> { - match self { - Executable::Native(nm) => Method::Native(nm.method), - Executable::Action(bm) => Method::Bytecode(bm.method), - } + self.method } pub fn debug_full_name(&self) -> WString { @@ -235,47 +92,135 @@ impl<'gc> Executable<'gc> { } pub fn num_parameters(&self) -> usize { - match self { - Executable::Native(NativeExecutable { method, .. }) => method.signature.len(), - Executable::Action(BytecodeExecutable { method, .. }) => method.signature.len(), + match self.method { + Method::Native(method) => method.signature.len(), + Method::Bytecode(method) => method.signature.len(), } } pub fn signature(&self) -> &[ParamConfig<'gc>] { - match self { - Executable::Native(NativeExecutable { method, .. }) => &method.signature, - Executable::Action(BytecodeExecutable { method, .. }) => method.signature(), + match &self.method { + Method::Native(method) => &method.signature, + Method::Bytecode(method) => method.signature(), } } pub fn is_variadic(&self) -> bool { - match self { - Executable::Native(NativeExecutable { method, .. }) => method.is_variadic, - Executable::Action(BytecodeExecutable { method, .. }) => method.is_variadic(), + match self.method { + Method::Native(method) => method.is_variadic, + Method::Bytecode(method) => method.is_variadic(), } } pub fn return_type(&self) -> &Multiname<'gc> { - match self { - Executable::Native(NativeExecutable { method, .. }) => &method.return_type, - Executable::Action(BytecodeExecutable { method, .. }) => &method.return_type, + match &self.method { + Method::Native(method) => &method.return_type, + Method::Bytecode(method) => &method.return_type, } } } -impl<'gc> fmt::Debug for Executable<'gc> { +/// Execute a method. +/// +/// The function will either be called directly if it is a Rust builtin, or +/// executed on the same AVM2 instance as the activation passed in here. +/// The value returned in either case will be provided here. +/// +/// It is a panicking logic error to attempt to execute user code while any +/// reachable object is currently under a GcCell write lock. +/// +/// Passed-in arguments will be conformed to the set of method parameters +/// declared on the function. +pub fn exec<'gc>( + method: Method<'gc>, + scope: ScopeChain<'gc>, + receiver: Object<'gc>, + bound_class: Option>, + mut arguments: &[Value<'gc>], + activation: &mut Activation<'_, 'gc>, + callee: Object<'gc>, +) -> Result, Error<'gc>> { + let ret = match method { + Method::Native(bm) => { + let caller_domain = activation.caller_domain(); + let caller_movie = activation.caller_movie(); + let mut activation = Activation::from_builtin( + activation.context.reborrow(), + bound_class, + scope, + caller_domain, + caller_movie, + ); + + if arguments.len() > bm.signature.len() && !bm.is_variadic { + return Err(format!( + "Attempted to call {:?} with {} arguments (more than {} is prohibited)", + bm.name, + arguments.len(), + bm.signature.len() + ) + .into()); + } + + if bm.resolved_signature.read().is_none() { + bm.resolve_signature(&mut activation)?; + } + + let resolved_signature = bm.resolved_signature.read(); + let resolved_signature = resolved_signature.as_ref().unwrap(); + + let arguments = activation.resolve_parameters( + method, + arguments, + resolved_signature, + Some(callee), + )?; + activation + .context + .avm2 + .push_call(activation.context.gc_context, method, bound_class); + (bm.method)(&mut activation, receiver, &arguments) + } + Method::Bytecode(bm) => { + if bm.is_unchecked() { + let max_args = bm.signature().len(); + if arguments.len() > max_args && !bm.is_variadic() { + arguments = &arguments[..max_args]; + } + } + + // This used to be a one step called Activation::from_method, + // but avoiding moving an Activation around helps perf + let mut activation = Activation::from_nothing(activation.context.reborrow()); + activation.init_from_method(bm, scope, receiver, arguments, bound_class, callee)?; + activation + .context + .avm2 + .push_call(activation.context.gc_context, method, bound_class); + activation.run_actions(bm) + } + }; + activation + .context + .avm2 + .pop_call(activation.context.gc_context); + ret +} + +impl<'gc> fmt::Debug for BoundMethod<'gc> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Action(be) => fmt - .debug_struct("Executable::Action") - .field("method", &Gc::as_ptr(be.method)) - .field("scope", &be.scope) - .field("receiver", &be.receiver) + match self.method { + Method::Bytecode(be) => fmt + .debug_struct("BoundMethod") + .field("method", &Gc::as_ptr(be)) + .field("scope", &self.scope) + .field("receiver", &self.bound_receiver) .finish(), - Self::Native(bm) => fmt - .debug_struct("Executable::Native") - .field("method", &bm.method) - .field("bound_receiver", &bm.bound_receiver) + Method::Native(bm) => fmt + .debug_struct("BoundMethod") + .field("method", &bm) + .field("scope", &self.scope) + .field("bound_receiver", &self.bound_receiver) .finish(), } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index e497ed8a4..b88767541 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -7,7 +7,7 @@ use crate::avm2::class::Class; use crate::avm2::domain::Domain; use crate::avm2::error; use crate::avm2::events::{DispatchList, Event}; -use crate::avm2::function::Executable; +use crate::avm2::function::{exec, BoundMethod}; use crate::avm2::property::Property; use crate::avm2::regexp::RegExp; use crate::avm2::value::{Hint, Value}; @@ -600,11 +600,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy class, } = full_method; - return Executable::from_method(method, scope, None, Some(class)).exec( - Value::from(self.into()), + return exec( + method, + scope, + self.into(), + Some(class), arguments, activation, - class.into(), //Deliberately invalid. + class.into(), //Callee deliberately invalid. ); } @@ -1153,8 +1156,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } - /// Get this object's `Executable`, if it has one. - fn as_executable(&self) -> Option>> { + /// Get this object's `BoundMethod`, if it has one. + fn as_executable(&self) -> Option>> { None } diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 30464b37d..233db2135 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Allocator, AllocatorFn, Class, ClassHashWrapper}; use crate::avm2::error::{argument_error, make_error_1127, reference_error, type_error}; -use crate::avm2::function::Executable; +use crate::avm2::function::exec; use crate::avm2::method::Method; use crate::avm2::object::function_object::FunctionObject; use crate::avm2::object::script_object::{scriptobject_allocator, ScriptObjectData}; @@ -477,10 +477,16 @@ impl<'gc> ClassObject<'gc> { activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { let scope = self.0.read().instance_scope; - let constructor = - Executable::from_method(self.0.read().constructor, scope, None, Some(self)); - - constructor.exec(receiver, arguments, activation, self.into()) + let method = self.0.read().constructor; + exec( + method, + scope, + receiver.coerce_to_object(activation)?, + Some(self), + arguments, + activation, + self.into(), + ) } /// Call the instance's native initializer. @@ -495,10 +501,16 @@ impl<'gc> ClassObject<'gc> { activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { let scope = self.0.read().instance_scope; - let constructor = - Executable::from_method(self.0.read().native_constructor, scope, None, Some(self)); - - constructor.exec(receiver, arguments, activation, self.into()) + let method = self.0.read().native_constructor; + exec( + method, + scope, + receiver.coerce_to_object(activation)?, + Some(self), + arguments, + activation, + self.into(), + ) } /// Supercall a method defined in this class. @@ -842,9 +854,15 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { ) -> Result, Error<'gc>> { if let Some(call_handler) = self.0.read().call_handler { let scope = self.0.read().class_scope; - let func = Executable::from_method(call_handler, scope, None, Some(self)); - - func.exec(receiver, arguments, activation, self.into()) + exec( + call_handler, + scope, + receiver.coerce_to_object(activation)?, + Some(self), + arguments, + activation, + self.into(), + ) } else if arguments.len() == 1 { arguments[0].coerce_to_type(activation, self.inner_class_definition()) } else { diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index 6b7e0f397..d7422ee4e 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -1,7 +1,7 @@ //! Function object impl use crate::avm2::activation::Activation; -use crate::avm2::function::Executable; +use crate::avm2::function::BoundMethod; use crate::avm2::method::{Method, NativeMethod}; use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData}; use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject}; @@ -41,7 +41,7 @@ pub fn function_allocator<'gc>( activation.context.gc_context, FunctionObjectData { base, - exec: Executable::from_method( + exec: BoundMethod::from_method( Method::Native(dummy), activation.create_scopechain(), None, @@ -78,7 +78,7 @@ pub struct FunctionObjectData<'gc> { base: ScriptObjectData<'gc>, /// Executable code - exec: Executable<'gc>, + exec: BoundMethod<'gc>, /// Attached prototype (note: not the same thing as base object's proto) prototype: Option>, @@ -120,7 +120,7 @@ impl<'gc> FunctionObject<'gc> { subclass_object: Option>, ) -> FunctionObject<'gc> { let fn_class = activation.avm2().classes().function; - let exec = Executable::from_method(method, scope, receiver, subclass_object); + let exec = BoundMethod::from_method(method, scope, receiver, subclass_object); FunctionObject(GcCell::new( activation.context.gc_context, @@ -169,7 +169,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { Ok(Value::Object(Object::from(*self))) } - fn as_executable(&self) -> Option>> { + fn as_executable(&self) -> Option>> { Some(Ref::map(self.0.read(), |r| &r.exec)) } diff --git a/core/src/avm2/specification.rs b/core/src/avm2/specification.rs index 1a16b05fb..763342b47 100644 --- a/core/src/avm2/specification.rs +++ b/core/src/avm2/specification.rs @@ -1,5 +1,5 @@ use crate::avm2::dynamic_map::DynamicKey; -use crate::avm2::function::Executable; +use crate::avm2::function::BoundMethod; use crate::avm2::method::{Method, ParamConfig}; use crate::avm2::object::TObject; use crate::avm2::traits::{Trait, TraitKind}; @@ -166,7 +166,7 @@ impl FunctionInfo { } } - pub fn from_executable(executable: &Executable, stubbed: bool) -> Self { + pub fn from_bound_method(executable: &BoundMethod, stubbed: bool) -> Self { Self { returns: executable .return_type() @@ -345,7 +345,7 @@ impl Definition { if let Some(executable) = object.as_executable() { output.get_or_insert_with(Default::default).function.insert( name.to_string(), - FunctionInfo::from_executable(&executable, false), + FunctionInfo::from_bound_method(&executable, false), ); } } else { @@ -494,7 +494,7 @@ pub fn capture_specification(context: &mut UpdateContext, output: &Path) { .get_or_insert_with(Default::default); instance_traits.function.insert( name.to_string(), - FunctionInfo::from_executable( + FunctionInfo::from_bound_method( &executable, namespace_stubs.has_method(&name.to_string()), ),