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:
parent
f3dee5c310
commit
1b67bb94c8
102
core/src/avm2.rs
102
core/src/avm2.rs
|
@ -1,7 +1,7 @@
|
||||||
//! ActionScript Virtual Machine 2 (AS3) support
|
//! ActionScript Virtual Machine 2 (AS3) support
|
||||||
|
|
||||||
use crate::avm2::activation::{Activation, Avm2ScriptEntry};
|
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::globals::SystemPrototypes;
|
||||||
use crate::avm2::names::{Multiname, Namespace, QName};
|
use crate::avm2::names::{Multiname, Namespace, QName};
|
||||||
use crate::avm2::object::{Object, TObject};
|
use crate::avm2::object::{Object, TObject};
|
||||||
|
@ -447,6 +447,10 @@ impl<'gc> Avm2<'gc> {
|
||||||
self.op_call_prop_void(context, index, num_args)
|
self.op_call_prop_void(context, index, num_args)
|
||||||
}
|
}
|
||||||
Op::CallStatic { index, num_args } => self.op_call_static(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::ReturnValue => self.op_return_value(context),
|
||||||
Op::ReturnVoid => self.op_return_void(context),
|
Op::ReturnVoid => self.op_return_void(context),
|
||||||
Op::GetProperty { index } => self.op_get_property(context, index),
|
Op::GetProperty { index } => self.op_get_property(context, index),
|
||||||
|
@ -469,6 +473,7 @@ impl<'gc> Avm2<'gc> {
|
||||||
Op::ConstructProp { index, num_args } => {
|
Op::ConstructProp { index, num_args } => {
|
||||||
self.op_construct_prop(context, 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::NewActivation => self.op_new_activation(context),
|
||||||
Op::NewObject { num_args } => self.op_new_object(context, num_args),
|
Op::NewObject { num_args } => self.op_new_object(context, num_args),
|
||||||
Op::NewFunction { index } => self.op_new_function(context, index),
|
Op::NewFunction { index } => self.op_new_function(context, index),
|
||||||
|
@ -706,6 +711,72 @@ impl<'gc> Avm2<'gc> {
|
||||||
Ok(())
|
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> {
|
fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
let return_value = self.pop();
|
let return_value = self.pop();
|
||||||
|
|
||||||
|
@ -1034,6 +1105,35 @@ impl<'gc> Avm2<'gc> {
|
||||||
Ok(())
|
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> {
|
fn op_new_activation(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
|
||||||
self.push(ScriptObject::bare_object(context.gc_context));
|
self.push(ScriptObject::bare_object(context.gc_context));
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,11 @@ pub struct Activation<'gc> {
|
||||||
///
|
///
|
||||||
/// A `scope` of `None` indicates that the scope stack is empty.
|
/// A `scope` of `None` indicates that the scope stack is empty.
|
||||||
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
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> {
|
impl<'gc> Activation<'gc> {
|
||||||
|
@ -158,6 +163,7 @@ impl<'gc> Activation<'gc> {
|
||||||
return_value: None,
|
return_value: None,
|
||||||
local_scope: ScriptObject::bare_object(context.gc_context),
|
local_scope: ScriptObject::bare_object(context.gc_context),
|
||||||
scope,
|
scope,
|
||||||
|
base_proto: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +172,7 @@ impl<'gc> Activation<'gc> {
|
||||||
action: &Avm2Function<'gc>,
|
action: &Avm2Function<'gc>,
|
||||||
this: Option<Object<'gc>>,
|
this: Option<Object<'gc>>,
|
||||||
arguments: &[Value<'gc>],
|
arguments: &[Value<'gc>],
|
||||||
|
base_proto: Option<Object<'gc>>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let method = action.method.clone();
|
let method = action.method.clone();
|
||||||
let scope = action.scope;
|
let scope = action.scope;
|
||||||
|
@ -198,6 +205,7 @@ impl<'gc> Activation<'gc> {
|
||||||
return_value: None,
|
return_value: None,
|
||||||
local_scope: ScriptObject::bare_object(context.gc_context),
|
local_scope: ScriptObject::bare_object(context.gc_context),
|
||||||
scope,
|
scope,
|
||||||
|
base_proto,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,4 +280,10 @@ impl<'gc> Activation<'gc> {
|
||||||
pub fn set_return_value(&mut self, value: Value<'gc>) {
|
pub fn set_return_value(&mut self, value: Value<'gc>) {
|
||||||
self.return_value = Some(value);
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,13 @@ unsafe impl<'gc> Collect for Executable<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> 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(
|
pub fn exec(
|
||||||
&self,
|
&self,
|
||||||
avm: &mut Avm2<'gc>,
|
avm: &mut Avm2<'gc>,
|
||||||
|
@ -137,9 +144,42 @@ impl<'gc> Executable<'gc> {
|
||||||
match self {
|
match self {
|
||||||
Executable::Native(nf) => nf(avm, context, reciever, arguments),
|
Executable::Native(nf) => nf(avm, context, reciever, arguments),
|
||||||
Executable::Action(a2f) => {
|
Executable::Action(a2f) => {
|
||||||
|
let base_proto = reciever.and_then(|o| o.proto());
|
||||||
let activation = GcCell::allocate(
|
let activation = GcCell::allocate(
|
||||||
context.gc_context,
|
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);
|
avm.insert_stack_frame(activation);
|
||||||
|
@ -322,6 +362,11 @@ impl<'gc> FunctionObject<'gc> {
|
||||||
QName::new(Namespace::public_namespace(), "prototype"),
|
QName::new(Namespace::public_namespace(), "prototype"),
|
||||||
class_proto.into(),
|
class_proto.into(),
|
||||||
)?;
|
)?;
|
||||||
|
class_proto.install_dynamic_property(
|
||||||
|
context.gc_context,
|
||||||
|
QName::new(Namespace::public_namespace(), "constructor"),
|
||||||
|
constr.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(constr)
|
Ok(constr)
|
||||||
}
|
}
|
||||||
|
@ -367,25 +412,31 @@ impl<'gc> FunctionObject<'gc> {
|
||||||
pub fn from_builtin_constr(
|
pub fn from_builtin_constr(
|
||||||
mc: MutationContext<'gc, '_>,
|
mc: MutationContext<'gc, '_>,
|
||||||
constr: NativeFunction<'gc>,
|
constr: NativeFunction<'gc>,
|
||||||
prototype: Object<'gc>,
|
mut prototype: Object<'gc>,
|
||||||
fn_proto: Object<'gc>,
|
fn_proto: Object<'gc>,
|
||||||
) -> Result<Object<'gc>, Error> {
|
) -> Result<Object<'gc>, Error> {
|
||||||
let mut base = ScriptObjectData::base_new(Some(fn_proto));
|
let mut base: Object<'gc> = FunctionObject(GcCell::allocate(
|
||||||
|
|
||||||
base.install_dynamic_property(
|
|
||||||
QName::new(Namespace::public_namespace(), "prototype"),
|
|
||||||
prototype.into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(FunctionObject(GcCell::allocate(
|
|
||||||
mc,
|
mc,
|
||||||
FunctionObjectData {
|
FunctionObjectData {
|
||||||
base,
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
||||||
exec: Some(constr.into()),
|
exec: Some(constr.into()),
|
||||||
class: None,
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue