From 1b67bb94c85b27afc52fac1b5e2fa9afc2e6a2a5 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 25 Feb 2020 18:07:53 -0500 Subject: [PATCH] Impl `callsuper`, `callsupervoid`, and `constructsuper`. This works primarily by retaining the current superclass prototype in the activation object and then using it to retrieve the super method. For constructors, we implement the `constructor` property, which is probably not the correct way to do this. --- core/src/avm2.rs | 102 +++++++++++++++++++++++++++++++++++- core/src/avm2/activation.rs | 14 +++++ core/src/avm2/function.rs | 75 +++++++++++++++++++++----- 3 files changed, 178 insertions(+), 13 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c9a83a9eb..2b2b56a1c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -1,7 +1,7 @@ //! ActionScript Virtual Machine 2 (AS3) support use crate::avm2::activation::{Activation, Avm2ScriptEntry}; -use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject}; +use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::globals::SystemPrototypes; use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::object::{Object, TObject}; @@ -447,6 +447,10 @@ impl<'gc> Avm2<'gc> { self.op_call_prop_void(context, index, num_args) } Op::CallStatic { index, num_args } => self.op_call_static(context, index, num_args), + Op::CallSuper { index, num_args } => self.op_call_super(context, index, num_args), + Op::CallSuperVoid { index, num_args } => { + self.op_call_super_void(context, index, num_args) + } Op::ReturnValue => self.op_return_value(context), Op::ReturnVoid => self.op_return_void(context), Op::GetProperty { index } => self.op_get_property(context, index), @@ -469,6 +473,7 @@ impl<'gc> Avm2<'gc> { Op::ConstructProp { index, num_args } => { self.op_construct_prop(context, index, num_args) } + Op::ConstructSuper { num_args } => self.op_construct_super(context, num_args), Op::NewActivation => self.op_new_activation(context), Op::NewObject { num_args } => self.op_new_object(context, num_args), Op::NewFunction { index } => self.op_new_function(context, index), @@ -706,6 +711,72 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_call_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let args = self.pop_args(arg_count); + let multiname = self.pool_multiname(index)?; + let receiver = self.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function.as_executable().ok_or_else(|| { + format!("Super method {:?} is not callable", multiname.local_name()).into() + }); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .push(self); + + Ok(()) + } + + fn op_call_super_void( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + index: Index, + arg_count: u32, + ) -> Result<(), Error> { + let args = self.pop_args(arg_count); + let multiname = self.pool_multiname(index)?; + let receiver = self.pop().as_object()?; + let name: Result = receiver + .resolve_multiname(&multiname) + .ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into()); + let function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name?, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function.as_executable().ok_or_else(|| { + format!("Super method {:?} is not callable", multiname.local_name()).into() + }); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .resolve(self, context)?; + + Ok(()) + } + fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { let return_value = self.pop(); @@ -1034,6 +1105,35 @@ impl<'gc> Avm2<'gc> { Ok(()) } + fn op_construct_super( + &mut self, + context: &mut UpdateContext<'_, 'gc, '_>, + arg_count: u32, + ) -> Result<(), Error> { + let args = self.pop_args(arg_count); + let receiver = self.pop().as_object()?; + let name = QName::new(Namespace::public_namespace(), "constructor"); + let function = self + .current_stack_frame() + .unwrap() + .read() + .base_proto() + .unwrap_or(receiver) + .get_property(&name, self, context)? + .resolve(self, context)? + .as_object()?; + + let exec: Result, Error> = function + .as_executable() + .ok_or_else(|| "Super constructor is not callable".to_string().into()); + + exec? + .exec_super(self, context, Some(receiver), &args)? + .resolve(self, context)?; + + Ok(()) + } + fn op_new_activation(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { self.push(ScriptObject::bare_object(context.gc_context)); diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 2595e88ca..a1cca6c79 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -124,6 +124,11 @@ pub struct Activation<'gc> { /// /// A `scope` of `None` indicates that the scope stack is empty. scope: Option>>, + + /// The base prototype of `this`. + /// + /// This will not be available if this is not a method call. + base_proto: Option>, } impl<'gc> Activation<'gc> { @@ -158,6 +163,7 @@ impl<'gc> Activation<'gc> { return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, + base_proto: None, }) } @@ -166,6 +172,7 @@ impl<'gc> Activation<'gc> { action: &Avm2Function<'gc>, this: Option>, arguments: &[Value<'gc>], + base_proto: Option>, ) -> Result { let method = action.method.clone(); let scope = action.scope; @@ -198,6 +205,7 @@ impl<'gc> Activation<'gc> { return_value: None, local_scope: ScriptObject::bare_object(context.gc_context), scope, + base_proto, }) } @@ -272,4 +280,10 @@ impl<'gc> Activation<'gc> { pub fn set_return_value(&mut self, value: Value<'gc>) { self.return_value = Some(value); } + + /// Get the base prototype of the object that the currently executing + /// method was retrieved from, if one exists. + pub fn base_proto(&self) -> Option> { + self.base_proto + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 962a9f2b2..228b20e52 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -127,6 +127,13 @@ unsafe impl<'gc> Collect for Executable<'gc> { } impl<'gc> Executable<'gc> { + /// Execute a method. + /// + /// The function will either be called directly if it is a Rust builtin, or + /// placed on the stack of the passed-in AVM2 otherwise. As a result, we + /// return a `ReturnValue` which can be used to force execution of the + /// given stack frame and obtain it's return value or to push said value + /// onto the AVM operand stack. pub fn exec( &self, avm: &mut Avm2<'gc>, @@ -137,9 +144,42 @@ impl<'gc> Executable<'gc> { match self { Executable::Native(nf) => nf(avm, context, reciever, arguments), Executable::Action(a2f) => { + let base_proto = reciever.and_then(|o| o.proto()); let activation = GcCell::allocate( context.gc_context, - Activation::from_action(context, &a2f, reciever, arguments)?, + Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, + ); + + avm.insert_stack_frame(activation); + Ok(activation.into()) + } + } + } + + /// Execute a method that is the `super` of an existing method. + /// + /// The primary difference between `exec` and `exec_super` is that the + /// former always resets the `base_proto` to the current `reciever` while + /// the latter sets it to the next object up the prototype chain. The + /// latter behavior is necessary to ensure that chains of `callsuper` and + /// `constructsuper` operate correctly. + pub fn exec_super( + &self, + avm: &mut Avm2<'gc>, + context: &mut UpdateContext<'_, 'gc, '_>, + reciever: Option>, + arguments: &[Value<'gc>], + ) -> Result, Error> { + match self { + Executable::Native(nf) => nf(avm, context, reciever, arguments), + Executable::Action(a2f) => { + let base_proto = avm + .current_stack_frame() + .and_then(|sf| sf.read().base_proto()) + .and_then(|o| o.proto()); + let activation = GcCell::allocate( + context.gc_context, + Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, ); avm.insert_stack_frame(activation); @@ -322,6 +362,11 @@ impl<'gc> FunctionObject<'gc> { QName::new(Namespace::public_namespace(), "prototype"), class_proto.into(), )?; + class_proto.install_dynamic_property( + context.gc_context, + QName::new(Namespace::public_namespace(), "constructor"), + constr.into(), + )?; Ok(constr) } @@ -367,25 +412,31 @@ impl<'gc> FunctionObject<'gc> { pub fn from_builtin_constr( mc: MutationContext<'gc, '_>, constr: NativeFunction<'gc>, - prototype: Object<'gc>, + mut prototype: Object<'gc>, fn_proto: Object<'gc>, ) -> Result, Error> { - let mut base = ScriptObjectData::base_new(Some(fn_proto)); - - base.install_dynamic_property( - QName::new(Namespace::public_namespace(), "prototype"), - prototype.into(), - )?; - - Ok(FunctionObject(GcCell::allocate( + let mut base: Object<'gc> = FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base, + base: ScriptObjectData::base_new(Some(fn_proto)), exec: Some(constr.into()), class: None, }, )) - .into()) + .into(); + + base.install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "prototype"), + prototype.into(), + )?; + prototype.install_dynamic_property( + mc, + QName::new(Namespace::public_namespace(), "constructor"), + base.into(), + )?; + + Ok(base) } }