2020-02-06 04:15:03 +00:00
|
|
|
//! AVM2 executables.
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
use crate::avm2::activation::Activation;
|
2020-02-19 03:26:08 +00:00
|
|
|
use crate::avm2::names::{Namespace, QName};
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::object::{Object, ObjectPtr, TObject};
|
|
|
|
use crate::avm2::return_value::ReturnValue;
|
2020-02-12 23:52:00 +00:00
|
|
|
use crate::avm2::scope::Scope;
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::script_object::ScriptObjectData;
|
|
|
|
use crate::avm2::value::Value;
|
|
|
|
use crate::avm2::{Avm2, Error};
|
|
|
|
use crate::context::UpdateContext;
|
2020-02-14 04:33:24 +00:00
|
|
|
use gc_arena::{Collect, CollectionContext, GcCell, MutationContext};
|
2020-02-06 04:15:03 +00:00
|
|
|
use std::fmt;
|
|
|
|
use std::rc::Rc;
|
2020-02-14 04:33:24 +00:00
|
|
|
use swf::avm2::types::{
|
|
|
|
AbcFile, Class as AbcClass, Index, Instance as AbcInstance, Method as AbcMethod,
|
2020-02-20 04:10:21 +00:00
|
|
|
MethodBody as AbcMethodBody,
|
2020-02-14 04:33:24 +00:00
|
|
|
};
|
2020-02-06 04:15:03 +00:00
|
|
|
|
|
|
|
/// Represents a function defined in Ruffle's code.
|
|
|
|
///
|
|
|
|
/// Parameters are as follows:
|
|
|
|
///
|
|
|
|
/// * The AVM2 runtime
|
|
|
|
/// * The action context
|
|
|
|
/// * The current `this` object
|
|
|
|
/// * The arguments this function was called with
|
|
|
|
///
|
|
|
|
/// Native functions are allowed to return a value or `None`. `None` indicates
|
|
|
|
/// that the given value will not be returned on the stack and instead will
|
|
|
|
/// resolve on the AVM stack, as if you had called a non-native function. If
|
|
|
|
/// your function yields `None`, you must ensure that the top-most activation
|
|
|
|
/// in the AVM1 runtime will return with the value of this function.
|
|
|
|
pub type NativeFunction<'gc> = fn(
|
|
|
|
&mut Avm2<'gc>,
|
|
|
|
&mut UpdateContext<'_, 'gc, '_>,
|
2020-02-24 03:11:02 +00:00
|
|
|
Option<Object<'gc>>,
|
2020-02-06 04:15:03 +00:00
|
|
|
&[Value<'gc>],
|
|
|
|
) -> Result<ReturnValue<'gc>, Error>;
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
/// Represents a reference to an AVM2 method and body.
|
2020-02-06 04:15:03 +00:00
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(require_static)]
|
2020-02-12 23:52:00 +00:00
|
|
|
pub struct Avm2MethodEntry {
|
2020-02-06 04:15:03 +00:00
|
|
|
/// The ABC file this function was defined in.
|
|
|
|
pub abc: Rc<AbcFile>,
|
|
|
|
|
|
|
|
/// The ABC method this function uses.
|
|
|
|
pub abc_method: u32,
|
|
|
|
|
|
|
|
/// The ABC method body this function uses.
|
|
|
|
pub abc_method_body: u32,
|
|
|
|
}
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
impl Avm2MethodEntry {
|
2020-02-14 04:33:24 +00:00
|
|
|
/// Construct an `Avm2MethodEntry` from an `AbcFile` and method index.
|
|
|
|
///
|
|
|
|
/// The method body index will be determined by searching through the ABC
|
|
|
|
/// for a matching method. If none exists, this function returns `None`.
|
|
|
|
pub fn from_method_index(abc: Rc<AbcFile>, abc_method: Index<AbcMethod>) -> Option<Self> {
|
|
|
|
if abc.methods.get(abc_method.0 as usize).is_some() {
|
|
|
|
for (index, method_body) in abc.method_bodies.iter().enumerate() {
|
|
|
|
if method_body.method.0 == abc_method.0 {
|
|
|
|
return Some(Self {
|
|
|
|
abc,
|
|
|
|
abc_method: abc_method.0,
|
|
|
|
abc_method_body: index as u32,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the underlying ABC file.
|
2020-02-20 04:10:21 +00:00
|
|
|
#[allow(dead_code)]
|
2020-02-14 04:33:24 +00:00
|
|
|
pub fn abc(&self) -> Rc<AbcFile> {
|
|
|
|
self.abc.clone()
|
|
|
|
}
|
|
|
|
|
2020-02-12 23:52:00 +00:00
|
|
|
/// Get a reference to the ABC method entry this refers to.
|
|
|
|
pub fn method(&self) -> &AbcMethod {
|
|
|
|
self.abc.methods.get(self.abc_method as usize).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC method body entry this refers to.
|
|
|
|
pub fn body(&self) -> &AbcMethodBody {
|
|
|
|
self.abc
|
|
|
|
.method_bodies
|
|
|
|
.get(self.abc_method_body as usize)
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents an AVM2 function.
|
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Avm2Function<'gc> {
|
|
|
|
/// The AVM method entry used to create this function.
|
|
|
|
pub method: Avm2MethodEntry,
|
|
|
|
|
|
|
|
/// Closure scope stack at time of creation
|
|
|
|
pub scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
impl<'gc> Avm2Function<'gc> {
|
|
|
|
pub fn from_method(method: Avm2MethodEntry, scope: Option<GcCell<'gc, Scope<'gc>>>) -> Self {
|
|
|
|
Self { method, scope }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// Represents code that can be executed by some means.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum Executable<'gc> {
|
|
|
|
Native(NativeFunction<'gc>),
|
2020-02-12 23:52:00 +00:00
|
|
|
Action(Avm2Function<'gc>),
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl<'gc> Collect for Executable<'gc> {
|
|
|
|
fn trace(&self, cc: CollectionContext) {
|
|
|
|
match self {
|
|
|
|
Self::Action(a2f) => a2f.trace(cc),
|
|
|
|
Self::Native(_nf) => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-15 01:30:19 +00:00
|
|
|
impl<'gc> Executable<'gc> {
|
2020-02-25 23:07:53 +00:00
|
|
|
/// 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.
|
2020-02-15 01:30:19 +00:00
|
|
|
pub fn exec(
|
|
|
|
&self,
|
2020-02-24 03:11:02 +00:00
|
|
|
reciever: Option<Object<'gc>>,
|
2020-02-15 01:30:19 +00:00
|
|
|
arguments: &[Value<'gc>],
|
2020-02-25 23:07:53 +00:00
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-25 23:07:53 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
match self {
|
|
|
|
Executable::Native(nf) => nf(avm, context, reciever, arguments),
|
|
|
|
Executable::Action(a2f) => {
|
2020-02-15 01:30:19 +00:00
|
|
|
let activation = GcCell::allocate(
|
|
|
|
context.gc_context,
|
2020-02-25 23:07:53 +00:00
|
|
|
Activation::from_action(context, &a2f, reciever, arguments, base_proto)?,
|
2020-02-15 01:30:19 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
avm.insert_stack_frame(activation);
|
|
|
|
Ok(activation.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
impl<'gc> fmt::Debug for Executable<'gc> {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(),
|
|
|
|
Self::Native(nf) => fmt
|
|
|
|
.debug_tuple("Executable::Native")
|
|
|
|
.field(&format!("{:p}", nf))
|
|
|
|
.finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
impl<'gc> From<NativeFunction<'gc>> for Executable<'gc> {
|
|
|
|
fn from(nf: NativeFunction<'gc>) -> Self {
|
|
|
|
Self::Native(nf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> From<Avm2Function<'gc>> for Executable<'gc> {
|
|
|
|
fn from(a2f: Avm2Function<'gc>) -> Self {
|
|
|
|
Self::Action(a2f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents a reference to an AVM2 class.
|
|
|
|
///
|
|
|
|
/// For some reason, this comes in two parts, one for static properties (called
|
|
|
|
/// the "class") and one for dynamic properties (called the "instance", even
|
|
|
|
/// though it really defines what ES3/AS2 would call a prototype)
|
|
|
|
#[derive(Collect, Clone, Debug)]
|
|
|
|
#[collect(require_static)]
|
|
|
|
pub struct Avm2ClassEntry {
|
|
|
|
/// The ABC file this function was defined in.
|
|
|
|
pub abc: Rc<AbcFile>,
|
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
/// The ABC class (used to define static properties).
|
|
|
|
///
|
|
|
|
/// This is also the index of the ABC instance, which holds instance
|
|
|
|
/// properties.
|
2020-02-14 04:33:24 +00:00
|
|
|
pub abc_class: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Avm2ClassEntry {
|
2020-02-20 04:10:21 +00:00
|
|
|
/// Construct an `Avm2MethodEntry` from an `AbcFile` and method index.
|
|
|
|
///
|
|
|
|
/// This function returns `None` if the given class index does not resolve
|
|
|
|
/// to a valid ABC class, or a valid ABC instance. As mentioned in the type
|
|
|
|
/// documentation, ABC classes and instances are intended to be paired.
|
|
|
|
pub fn from_class_index(abc: Rc<AbcFile>, abc_class: Index<AbcClass>) -> Option<Self> {
|
|
|
|
if abc.classes.get(abc_class.0 as usize).is_some()
|
|
|
|
&& abc.instances.get(abc_class.0 as usize).is_some()
|
|
|
|
{
|
|
|
|
return Some(Self {
|
|
|
|
abc,
|
|
|
|
abc_class: abc_class.0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-02-14 04:33:24 +00:00
|
|
|
/// Get the underlying ABC file.
|
|
|
|
pub fn abc(&self) -> Rc<AbcFile> {
|
|
|
|
self.abc.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC class entry this refers to.
|
|
|
|
pub fn class(&self) -> &AbcClass {
|
|
|
|
self.abc.classes.get(self.abc_class as usize).unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get a reference to the ABC class instance entry this refers to.
|
|
|
|
pub fn instance(&self) -> &AbcInstance {
|
2020-02-20 04:10:21 +00:00
|
|
|
self.abc.instances.get(self.abc_class as usize).unwrap()
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// An Object which can be called to execute it's function code.
|
|
|
|
#[derive(Collect, Debug, Clone, Copy)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct FunctionObject<'gc>(GcCell<'gc, FunctionObjectData<'gc>>);
|
|
|
|
|
|
|
|
#[derive(Collect, Debug, Clone)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct FunctionObjectData<'gc> {
|
|
|
|
/// Base script object
|
|
|
|
base: ScriptObjectData<'gc>,
|
|
|
|
|
|
|
|
/// Executable code
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Option<Executable<'gc>>,
|
2020-02-14 04:33:24 +00:00
|
|
|
|
|
|
|
/// The class that defined this function object, if any.
|
|
|
|
class: Option<Avm2ClassEntry>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> FunctionObject<'gc> {
|
|
|
|
/// Construct a class from an ABC class/instance pair.
|
|
|
|
///
|
2020-02-26 01:49:37 +00:00
|
|
|
/// This function returns both the class itself, and the static class
|
|
|
|
/// initializer method that you should call before interacting with the
|
|
|
|
/// class. The latter should be called using the former as a reciever.
|
2020-02-14 04:33:24 +00:00
|
|
|
pub fn from_abc_class(
|
2020-02-20 04:10:21 +00:00
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-14 04:33:24 +00:00
|
|
|
class: Avm2ClassEntry,
|
2020-02-22 22:54:38 +00:00
|
|
|
base_class: Object<'gc>,
|
2020-02-20 04:10:21 +00:00
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
fn_proto: Object<'gc>,
|
2020-02-26 01:49:37 +00:00
|
|
|
) -> Result<(Object<'gc>, Object<'gc>), Error> {
|
2020-02-22 22:54:38 +00:00
|
|
|
let super_proto: Result<Object<'gc>, Error> = base_class
|
|
|
|
.get_property(
|
2020-02-28 04:38:56 +00:00
|
|
|
base_class,
|
2020-02-22 22:54:38 +00:00
|
|
|
&QName::new(Namespace::public_namespace(), "prototype"),
|
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
)?
|
|
|
|
.resolve(avm, context)?
|
|
|
|
.as_object()
|
|
|
|
.map_err(|_| {
|
|
|
|
let super_name = QName::from_abc_multiname(
|
|
|
|
&class.abc(),
|
|
|
|
class.instance().super_name.clone(),
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Ok(super_name) = super_name {
|
|
|
|
format!(
|
|
|
|
"Could not resolve superclass prototype {:?}",
|
|
|
|
super_name.local_name()
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
} else {
|
|
|
|
format!(
|
|
|
|
"Could not resolve superclass prototype, and got this error when getting it's name: {:?}",
|
|
|
|
super_name.unwrap_err()
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let mut class_proto = super_proto?.construct(avm, context, &[])?;
|
|
|
|
|
|
|
|
for trait_entry in class.instance().traits.iter() {
|
|
|
|
class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?;
|
|
|
|
}
|
|
|
|
|
2020-02-24 19:41:09 +00:00
|
|
|
let initializer_index = class.instance().init_method.clone();
|
2020-02-20 04:10:21 +00:00
|
|
|
let initializer: Result<Avm2MethodEntry, Error> =
|
|
|
|
Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else(
|
|
|
|
|| {
|
|
|
|
format!(
|
2020-02-24 19:41:09 +00:00
|
|
|
"Instance initializer method index {} does not exist",
|
2020-02-20 04:10:21 +00:00
|
|
|
initializer_index.0
|
|
|
|
)
|
|
|
|
.into()
|
2020-02-14 04:33:24 +00:00
|
|
|
},
|
2020-02-20 04:10:21 +00:00
|
|
|
);
|
2020-02-22 22:54:38 +00:00
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
FunctionObjectData {
|
2020-02-26 01:49:37 +00:00
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
2020-02-26 00:56:19 +00:00
|
|
|
exec: Some(Avm2Function::from_method(initializer?, scope).into()),
|
2020-02-20 04:10:21 +00:00
|
|
|
class: Some(class.clone()),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into();
|
|
|
|
|
|
|
|
for trait_entry in class.class().traits.iter() {
|
|
|
|
constr.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?;
|
|
|
|
}
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
constr.install_dynamic_property(
|
2020-02-20 04:10:21 +00:00
|
|
|
context.gc_context,
|
|
|
|
QName::new(Namespace::public_namespace(), "prototype"),
|
2020-02-24 03:11:02 +00:00
|
|
|
class_proto.into(),
|
|
|
|
)?;
|
2020-02-25 23:07:53 +00:00
|
|
|
class_proto.install_dynamic_property(
|
|
|
|
context.gc_context,
|
|
|
|
QName::new(Namespace::public_namespace(), "constructor"),
|
|
|
|
constr.into(),
|
|
|
|
)?;
|
2020-02-20 04:10:21 +00:00
|
|
|
|
2020-02-26 01:49:37 +00:00
|
|
|
let class_initializer_index = class.class().init_method.clone();
|
|
|
|
let class_initializer: Result<Avm2MethodEntry, Error> =
|
|
|
|
Avm2MethodEntry::from_method_index(class.abc(), class_initializer_index.clone())
|
|
|
|
.ok_or_else(|| {
|
|
|
|
format!(
|
|
|
|
"Class initializer method index {} does not exist",
|
|
|
|
class_initializer_index.0
|
|
|
|
)
|
|
|
|
.into()
|
|
|
|
});
|
|
|
|
let class_constr = FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
FunctionObjectData {
|
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
|
|
|
exec: Some(Avm2Function::from_method(class_initializer?, scope).into()),
|
|
|
|
class: Some(class.clone()),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into();
|
|
|
|
|
|
|
|
Ok((constr, class_constr))
|
2020-02-14 04:33:24 +00:00
|
|
|
}
|
2020-02-18 00:43:23 +00:00
|
|
|
|
|
|
|
/// Construct a function from an ABC method and the current closure scope.
|
|
|
|
pub fn from_abc_method(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
method: Avm2MethodEntry,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Object<'gc> {
|
2020-02-19 23:53:21 +00:00
|
|
|
let exec = Some(Avm2Function::from_method(method, scope).into());
|
2020-02-18 00:43:23 +00:00
|
|
|
|
|
|
|
FunctionObject(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
|
|
|
exec,
|
|
|
|
class: None,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into()
|
|
|
|
}
|
2020-02-19 03:26:08 +00:00
|
|
|
|
|
|
|
/// Construct a builtin function object from a Rust function.
|
|
|
|
pub fn from_builtin(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
nf: NativeFunction<'gc>,
|
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Object<'gc> {
|
|
|
|
FunctionObject(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Some(nf.into()),
|
2020-02-19 03:26:08 +00:00
|
|
|
class: None,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Construct a builtin type from a Rust constructor and prototype.
|
|
|
|
pub fn from_builtin_constr(
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
constr: NativeFunction<'gc>,
|
2020-02-25 23:07:53 +00:00
|
|
|
mut prototype: Object<'gc>,
|
2020-02-19 03:26:08 +00:00
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Result<Object<'gc>, Error> {
|
2020-02-25 23:07:53 +00:00
|
|
|
let mut base: Object<'gc> = FunctionObject(GcCell::allocate(
|
2020-02-19 03:26:08 +00:00
|
|
|
mc,
|
|
|
|
FunctionObjectData {
|
2020-02-25 23:07:53 +00:00
|
|
|
base: ScriptObjectData::base_new(Some(fn_proto)),
|
2020-02-19 23:53:21 +00:00
|
|
|
exec: Some(constr.into()),
|
2020-02-19 03:26:08 +00:00
|
|
|
class: None,
|
|
|
|
},
|
|
|
|
))
|
2020-02-25 23:07:53 +00:00
|
|
|
.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)
|
2020-02-19 03:26:08 +00:00
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> TObject<'gc> for FunctionObject<'gc> {
|
2020-02-26 02:37:47 +00:00
|
|
|
fn get_property_local(
|
2020-02-12 00:28:42 +00:00
|
|
|
self,
|
2020-02-28 04:38:56 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-15 01:30:19 +00:00
|
|
|
self.0
|
|
|
|
.read()
|
|
|
|
.base
|
2020-02-28 04:38:56 +00:00
|
|
|
.get_property_local(reciever, name, avm, context)
|
2020-02-12 00:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn set_property_local(
|
2020-02-12 00:28:42 +00:00
|
|
|
self,
|
2020-02-28 04:38:56 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-12 00:28:42 +00:00
|
|
|
self.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.base
|
2020-02-29 01:00:18 +00:00
|
|
|
.set_property_local(reciever, name, value, avm, context)
|
2020-02-12 00:28:42 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn init_property_local(
|
2020-02-22 21:21:28 +00:00
|
|
|
self,
|
2020-02-29 01:00:18 +00:00
|
|
|
reciever: Object<'gc>,
|
2020-02-22 21:21:28 +00:00
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-29 01:55:09 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-22 21:21:28 +00:00
|
|
|
self.0
|
|
|
|
.write(context.gc_context)
|
|
|
|
.base
|
2020-02-29 01:00:18 +00:00
|
|
|
.init_property_local(reciever, name, value, avm, context)
|
2020-02-22 21:21:28 +00:00
|
|
|
}
|
|
|
|
|
2020-02-21 19:52:24 +00:00
|
|
|
fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool {
|
|
|
|
self.0.write(gc_context).base.delete_property(multiname)
|
|
|
|
}
|
|
|
|
|
2020-02-19 19:17:33 +00:00
|
|
|
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error> {
|
|
|
|
self.0.read().base.get_slot(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.set_slot(id, value, mc)
|
|
|
|
}
|
|
|
|
|
2020-02-22 21:21:28 +00:00
|
|
|
fn init_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.init_slot(id, value, mc)
|
|
|
|
}
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
fn get_method(self, id: u32) -> Option<Object<'gc>> {
|
|
|
|
self.0.read().base.get_method(id)
|
|
|
|
}
|
|
|
|
|
2020-03-01 01:09:07 +00:00
|
|
|
fn resolve_any(self, local_name: &str) -> Option<Namespace> {
|
|
|
|
self.0.read().base.resolve_any(local_name)
|
|
|
|
}
|
|
|
|
|
2020-02-26 02:37:47 +00:00
|
|
|
fn has_own_property(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_own_property(name)
|
2020-02-12 00:42:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-29 02:37:25 +00:00
|
|
|
fn has_own_virtual_getter(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_own_virtual_getter(name)
|
|
|
|
}
|
|
|
|
|
2020-02-29 01:00:18 +00:00
|
|
|
fn has_own_virtual_setter(self, name: &QName) -> bool {
|
|
|
|
self.0.read().base.has_own_virtual_setter(name)
|
|
|
|
}
|
|
|
|
|
2020-02-13 03:47:56 +00:00
|
|
|
fn proto(&self) -> Option<Object<'gc>> {
|
|
|
|
self.0.read().base.proto()
|
|
|
|
}
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
fn as_ptr(&self) -> *const ObjectPtr {
|
|
|
|
self.0.as_ptr() as *const ObjectPtr
|
|
|
|
}
|
2020-02-08 03:42:04 +00:00
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
fn as_executable(&self) -> Option<Executable<'gc>> {
|
|
|
|
self.0.read().exec.clone()
|
|
|
|
}
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
fn call(
|
|
|
|
self,
|
2020-02-24 03:11:02 +00:00
|
|
|
reciever: Option<Object<'gc>>,
|
2020-02-08 03:42:04 +00:00
|
|
|
arguments: &[Value<'gc>],
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
base_proto: Option<Object<'gc>>,
|
2020-02-08 03:42:04 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
2020-02-19 23:53:21 +00:00
|
|
|
if let Some(exec) = &self.0.read().exec {
|
Refactor the base_proto system to more accurately record what prototype methods come from.
The previous system primarily relied on `Executable` to automatically start and continue a super chain. This works, but only for class hierarchies without *override gaps* - methods that override another method not defined by the direct superclass of the method. In that case, the override method would be called twice as the `base_class` was moved up one prototype at a time, which is wrong.
The new system relies on the call site to accurately report the prototype from which the current method was retrieved from. Super calls then start the resolution process *from the superclass of this prototype*, to ensure that the already-called method is skipped.
It should be noted that the proper `base_class` for things like `callmethod`, `callstatic`, `call`, `get`/`set` methods, and other call opcodes that don't use property look-up are best-effort guesses that may need to be amended later with better tests.
To facilitate `base_proto` resolution, a new `Object` method has been added. It's similar to `get_property`, but instead returns the closest prototype that can resolve the given `QName`, rather than the actual property's `ReturnValue`. Call operations use this to resolve the `base_proto`, and then resolve the method being called in `base_proto`. The existing `exec_super` method was removed and a `base_proto` method added to `exec` and `call`.
2020-02-28 02:58:59 +00:00
|
|
|
exec.exec(reciever, arguments, avm, context, base_proto)
|
2020-02-19 23:53:21 +00:00
|
|
|
} else {
|
|
|
|
Err("Not a callable function!".into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn construct(
|
|
|
|
&self,
|
|
|
|
_avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
_args: &[Value<'gc>],
|
|
|
|
) -> Result<Object<'gc>, Error> {
|
|
|
|
let this: Object<'gc> = Object::FunctionObject(*self);
|
|
|
|
let base = ScriptObjectData::base_new(Some(this));
|
|
|
|
|
|
|
|
Ok(FunctionObject(GcCell::allocate(
|
|
|
|
context.gc_context,
|
|
|
|
FunctionObjectData {
|
|
|
|
base,
|
|
|
|
exec: None,
|
|
|
|
class: None,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
.into())
|
2020-02-08 03:42:04 +00:00
|
|
|
}
|
2020-02-19 01:08:13 +00:00
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
fn install_method(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
|
|
|
) {
|
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.install_method(name, disp_id, function)
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn install_getter(
|
2020-02-19 01:08:13 +00:00
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
2020-02-20 04:10:21 +00:00
|
|
|
name: QName,
|
2020-02-24 03:11:02 +00:00
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
2020-02-19 01:08:13 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-02-24 03:11:02 +00:00
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.install_getter(name, disp_id, function)
|
2020-02-19 01:08:13 +00:00
|
|
|
}
|
2020-02-19 03:26:08 +00:00
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
fn install_setter(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
2020-02-24 03:11:02 +00:00
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
2020-02-20 04:10:21 +00:00
|
|
|
) -> Result<(), Error> {
|
2020-02-24 03:11:02 +00:00
|
|
|
self.0
|
|
|
|
.write(mc)
|
|
|
|
.base
|
|
|
|
.install_setter(name, disp_id, function)
|
2020-02-19 03:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn install_dynamic_property(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
self.0.write(mc).base.install_dynamic_property(name, value)
|
|
|
|
}
|
2020-02-21 02:02:49 +00:00
|
|
|
|
|
|
|
fn install_slot(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) {
|
|
|
|
self.0.write(mc).base.install_slot(name, id, value)
|
|
|
|
}
|
2020-02-21 04:20:46 +00:00
|
|
|
|
|
|
|
fn install_const(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) {
|
|
|
|
self.0.write(mc).base.install_const(name, id, value)
|
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|