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.
This commit is contained in:
David Wendt 2020-02-25 18:07:53 -05:00
parent f3dee5c310
commit 1b67bb94c8
3 changed files with 178 additions and 13 deletions

View File

@ -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<AbcMultiname>,
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<QName, Error> = 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<Executable<'gc>, 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<AbcMultiname>,
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<QName, Error> = 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<Executable<'gc>, 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<Executable<'gc>, 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));

View File

@ -124,6 +124,11 @@ pub struct Activation<'gc> {
///
/// A `scope` of `None` indicates that the scope stack is empty.
scope: Option<GcCell<'gc, Scope<'gc>>>,
/// The base prototype of `this`.
///
/// This will not be available if this is not a method call.
base_proto: Option<Object<'gc>>,
}
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<Object<'gc>>,
arguments: &[Value<'gc>],
base_proto: Option<Object<'gc>>,
) -> Result<Self, Error> {
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<Object<'gc>> {
self.base_proto
}
}

View File

@ -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<Object<'gc>>,
arguments: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, 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<Object<'gc>, 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)
}
}