2020-02-05 18:52:11 +00:00
|
|
|
//! AVM2 objects.
|
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, Executable, FunctionObject};
|
2020-02-22 22:54:38 +00:00
|
|
|
use crate::avm2::names::{Multiname, QName};
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::return_value::ReturnValue;
|
2020-02-19 01:08:13 +00:00
|
|
|
use crate::avm2::scope::Scope;
|
2020-02-05 18:52:11 +00:00
|
|
|
use crate::avm2::script_object::ScriptObject;
|
2020-02-21 03:04:31 +00:00
|
|
|
use crate::avm2::value::{abc_default_value, Value};
|
2020-02-06 04:15:03 +00:00
|
|
|
use crate::avm2::{Avm2, Error};
|
|
|
|
use crate::context::UpdateContext;
|
2020-02-19 01:08:13 +00:00
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
2020-02-05 18:52:11 +00:00
|
|
|
use ruffle_macros::enum_trait_object;
|
|
|
|
use std::fmt::Debug;
|
2020-02-19 01:08:13 +00:00
|
|
|
use std::rc::Rc;
|
2020-02-20 04:10:21 +00:00
|
|
|
use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind};
|
2020-02-05 18:52:11 +00:00
|
|
|
|
|
|
|
/// Represents an object that can be directly interacted with by the AVM2
|
|
|
|
/// runtime.
|
|
|
|
#[enum_trait_object(
|
|
|
|
#[derive(Clone, Collect, Debug, Copy)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub enum Object<'gc> {
|
2020-02-06 04:15:03 +00:00
|
|
|
ScriptObject(ScriptObject<'gc>),
|
|
|
|
FunctionObject(FunctionObject<'gc>)
|
2020-02-05 18:52:11 +00:00
|
|
|
}
|
|
|
|
)]
|
|
|
|
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
|
2020-02-26 02:37:47 +00:00
|
|
|
/// Retrieve a property by it's QName, without taking prototype lookups
|
|
|
|
/// into account.
|
|
|
|
fn get_property_local(
|
|
|
|
self,
|
|
|
|
name: &QName,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<ReturnValue<'gc>, Error>;
|
|
|
|
|
2020-02-05 18:52:11 +00:00
|
|
|
/// Retrieve a property by it's QName.
|
2020-02-06 04:15:03 +00:00
|
|
|
fn get_property(
|
|
|
|
self,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
2020-02-26 02:37:47 +00:00
|
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
|
|
if self.has_own_property(name) {
|
|
|
|
return self.get_property_local(name, avm, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(proto) = self.proto() {
|
|
|
|
return proto.get_property(name, avm, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::Undefined.into())
|
|
|
|
}
|
2020-02-05 18:52:11 +00:00
|
|
|
|
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
|
|
|
/// Retrieve the base prototype that a particular QName is defined in.
|
|
|
|
fn get_base_proto(self, name: &QName) -> Option<Object<'gc>> {
|
|
|
|
if self.has_own_property(name) {
|
|
|
|
return Some(self.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(proto) = self.proto() {
|
|
|
|
return proto.get_base_proto(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-02-05 18:52:11 +00:00
|
|
|
/// Set a property by it's QName.
|
2020-02-06 04:15:03 +00:00
|
|
|
fn set_property(
|
2020-02-10 19:54:55 +00:00
|
|
|
self,
|
2020-02-12 00:28:42 +00:00
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error>;
|
2020-02-06 04:15:03 +00:00
|
|
|
|
2020-02-22 21:21:28 +00:00
|
|
|
/// Init a property by it's QName.
|
|
|
|
fn init_property(
|
|
|
|
self,
|
|
|
|
name: &QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
) -> Result<(), Error>;
|
|
|
|
|
2020-02-19 19:17:33 +00:00
|
|
|
/// Retrieve a slot by it's index.
|
|
|
|
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error>;
|
|
|
|
|
|
|
|
/// Set a slot by it's index.
|
|
|
|
fn set_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error>;
|
2020-02-22 21:21:28 +00:00
|
|
|
|
|
|
|
/// Initialize a slot by it's index.
|
|
|
|
fn init_slot(
|
|
|
|
self,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error>;
|
2020-02-19 19:17:33 +00:00
|
|
|
|
2020-02-24 03:11:02 +00:00
|
|
|
/// Retrieve a method by it's index.
|
|
|
|
fn get_method(self, id: u32) -> Option<Object<'gc>>;
|
|
|
|
|
2020-02-11 04:28:05 +00:00
|
|
|
/// Resolve a multiname into a single QName, if any of the namespaces
|
|
|
|
/// match.
|
2020-02-11 23:58:25 +00:00
|
|
|
fn resolve_multiname(self, multiname: &Multiname) -> Option<QName> {
|
|
|
|
for ns in multiname.namespace_set() {
|
2020-02-21 00:05:33 +00:00
|
|
|
let qname = QName::new(ns.clone(), multiname.local_name()?);
|
2020-02-11 23:58:25 +00:00
|
|
|
if self.has_property(&qname) {
|
|
|
|
return Some(qname);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-26 02:37:47 +00:00
|
|
|
if let Some(proto) = self.proto() {
|
2020-02-27 02:54:29 +00:00
|
|
|
return proto.resolve_multiname(multiname);
|
2020-02-26 02:37:47 +00:00
|
|
|
}
|
|
|
|
|
2020-02-11 04:28:05 +00:00
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-02-10 19:54:55 +00:00
|
|
|
/// Indicates whether or not a property exists on an object.
|
2020-02-26 02:37:47 +00:00
|
|
|
fn has_property(self, name: &QName) -> bool {
|
|
|
|
if self.has_own_property(name) {
|
|
|
|
true
|
|
|
|
} else if let Some(proto) = self.proto() {
|
|
|
|
proto.has_own_property(name)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2020-02-10 19:54:55 +00:00
|
|
|
|
|
|
|
/// Indicates whether or not a property exists on an object and is not part
|
|
|
|
/// of the prototype chain.
|
2020-02-26 02:37:47 +00:00
|
|
|
fn has_own_property(self, name: &QName) -> bool;
|
2020-02-10 19:54:55 +00:00
|
|
|
|
|
|
|
/// Indicates whether or not a property is overwritable.
|
|
|
|
fn is_property_overwritable(self, _name: &QName) -> bool {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete a named property from the object.
|
|
|
|
///
|
|
|
|
/// Returns false if the property cannot be deleted.
|
2020-02-21 19:52:24 +00:00
|
|
|
fn delete_property(&self, gc_context: MutationContext<'gc, '_>, multiname: &QName) -> bool;
|
2020-02-10 19:54:55 +00:00
|
|
|
|
|
|
|
/// Retrieve the `__proto__` of a given object.
|
|
|
|
///
|
|
|
|
/// The proto is another object used to resolve methods across a class of
|
|
|
|
/// multiple objects. It should also be accessible as `__proto__` from
|
|
|
|
/// `get`.
|
2020-02-13 03:47:56 +00:00
|
|
|
fn proto(&self) -> Option<Object<'gc>>;
|
2020-02-10 19:54:55 +00:00
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
/// Install a method (or any other non-slot value) on an object.
|
2020-02-24 03:11:02 +00:00
|
|
|
fn install_method(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
disp_id: u32,
|
|
|
|
function: Object<'gc>,
|
|
|
|
);
|
2020-02-20 04:10:21 +00:00
|
|
|
|
|
|
|
/// Install a getter method on an object property.
|
|
|
|
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-20 04:10:21 +00:00
|
|
|
/// Install a setter method on an object property.
|
|
|
|
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-19 03:26:08 +00:00
|
|
|
|
|
|
|
/// Install a dynamic or built-in value property on an object.
|
|
|
|
fn install_dynamic_property(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
value: Value<'gc>,
|
|
|
|
) -> Result<(), Error>;
|
|
|
|
|
2020-02-21 02:02:49 +00:00
|
|
|
/// Install a slot on an object property.
|
|
|
|
fn install_slot(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
);
|
|
|
|
|
2020-02-21 04:20:46 +00:00
|
|
|
/// Install a const on an object property.
|
|
|
|
fn install_const(
|
|
|
|
&mut self,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
name: QName,
|
|
|
|
id: u32,
|
|
|
|
value: Value<'gc>,
|
|
|
|
);
|
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
/// Install a trait from an ABC file on an object.
|
|
|
|
fn install_trait(
|
|
|
|
&mut self,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
abc: Rc<AbcFile>,
|
|
|
|
trait_entry: &AbcTrait,
|
|
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
|
|
fn_proto: Object<'gc>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?;
|
2020-02-20 19:41:15 +00:00
|
|
|
avm_debug!(
|
|
|
|
"Installing trait {:?} of kind {:?}",
|
|
|
|
trait_name,
|
|
|
|
trait_entry.kind
|
|
|
|
);
|
|
|
|
|
2020-02-20 04:10:21 +00:00
|
|
|
match &trait_entry.kind {
|
2020-02-21 03:04:31 +00:00
|
|
|
AbcTraitKind::Slot { slot_id, value, .. } => {
|
|
|
|
let value = if let Some(value) = value {
|
|
|
|
abc_default_value(&abc, value)?
|
|
|
|
} else {
|
|
|
|
Value::Undefined
|
|
|
|
};
|
|
|
|
self.install_slot(context.gc_context, trait_name, *slot_id, value);
|
|
|
|
}
|
2020-02-24 03:11:02 +00:00
|
|
|
AbcTraitKind::Method {
|
|
|
|
disp_id, method, ..
|
|
|
|
} => {
|
2020-02-20 04:10:21 +00:00
|
|
|
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
|
|
|
|
let function =
|
|
|
|
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
|
2020-02-24 03:11:02 +00:00
|
|
|
self.install_method(context.gc_context, trait_name, *disp_id, function);
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
2020-02-24 03:11:02 +00:00
|
|
|
AbcTraitKind::Getter {
|
|
|
|
disp_id, method, ..
|
|
|
|
} => {
|
2020-02-20 04:10:21 +00:00
|
|
|
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
|
2020-02-24 03:11:02 +00:00
|
|
|
let function =
|
|
|
|
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
|
|
|
|
self.install_getter(context.gc_context, trait_name, *disp_id, function)?;
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
2020-02-24 03:11:02 +00:00
|
|
|
AbcTraitKind::Setter {
|
|
|
|
disp_id, method, ..
|
|
|
|
} => {
|
2020-02-20 04:10:21 +00:00
|
|
|
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
|
2020-02-24 03:11:02 +00:00
|
|
|
let function =
|
|
|
|
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
|
|
|
|
self.install_setter(context.gc_context, trait_name, *disp_id, function)?;
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
2020-02-21 02:02:49 +00:00
|
|
|
AbcTraitKind::Class { slot_id, class } => {
|
2020-02-20 04:10:21 +00:00
|
|
|
let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap();
|
|
|
|
let super_name = QName::from_abc_multiname(
|
|
|
|
&type_entry.abc(),
|
|
|
|
type_entry.instance().super_name.clone(),
|
|
|
|
)?;
|
2020-02-21 00:34:48 +00:00
|
|
|
let super_class: Result<Object<'gc>, Error> = self
|
2020-02-20 04:10:21 +00:00
|
|
|
.get_property(&super_name, avm, context)?
|
|
|
|
.resolve(avm, context)?
|
2020-02-21 00:34:48 +00:00
|
|
|
.as_object()
|
|
|
|
.map_err(|_e| {
|
|
|
|
format!("Could not resolve superclass {:?}", super_name.local_name()).into()
|
|
|
|
});
|
2020-02-20 04:10:21 +00:00
|
|
|
|
2020-02-26 01:49:37 +00:00
|
|
|
let (class, _cinit) = FunctionObject::from_abc_class(
|
2020-02-20 04:10:21 +00:00
|
|
|
avm,
|
|
|
|
context,
|
|
|
|
type_entry.clone(),
|
2020-02-22 22:54:38 +00:00
|
|
|
super_class?,
|
2020-02-20 04:10:21 +00:00
|
|
|
scope,
|
|
|
|
fn_proto,
|
|
|
|
)?;
|
|
|
|
let class_name = QName::from_abc_multiname(
|
|
|
|
&type_entry.abc(),
|
|
|
|
type_entry.instance().name.clone(),
|
|
|
|
)?;
|
2020-02-21 04:20:46 +00:00
|
|
|
self.install_const(context.gc_context, class_name, *slot_id, class.into());
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
2020-02-21 03:13:24 +00:00
|
|
|
AbcTraitKind::Function {
|
|
|
|
slot_id, function, ..
|
|
|
|
} => {
|
|
|
|
let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap();
|
|
|
|
let function =
|
|
|
|
FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto);
|
2020-02-21 04:20:46 +00:00
|
|
|
self.install_const(context.gc_context, trait_name, *slot_id, function.into());
|
|
|
|
}
|
|
|
|
AbcTraitKind::Const { slot_id, value, .. } => {
|
|
|
|
let value = if let Some(value) = value {
|
|
|
|
abc_default_value(&abc, value)?
|
|
|
|
} else {
|
|
|
|
Value::Undefined
|
|
|
|
};
|
|
|
|
self.install_const(context.gc_context, trait_name, *slot_id, value);
|
2020-02-21 03:13:24 +00:00
|
|
|
}
|
2020-02-20 04:10:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-02-08 03:42:04 +00:00
|
|
|
/// Call the object.
|
|
|
|
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> {
|
|
|
|
Err("Object is not callable".into())
|
|
|
|
}
|
|
|
|
|
2020-02-19 23:53:21 +00:00
|
|
|
/// Construct a host object of some kind and return it's cell.
|
|
|
|
///
|
|
|
|
/// As the first step in object construction, the `construct` method is
|
|
|
|
/// called on the prototype to create a new object. The prototype may
|
|
|
|
/// construct any object implementation it wants, however, it's expected
|
|
|
|
/// to produce a like `TObject` implementor with itself as the new object's
|
|
|
|
/// proto.
|
|
|
|
///
|
|
|
|
/// After construction, the constructor function is `call`ed with the new
|
|
|
|
/// object as `this` to initialize the object.
|
|
|
|
///
|
|
|
|
/// The arguments passed to the constructor are provided here; however, all
|
|
|
|
/// object construction should happen in `call`, not `new`. `new` exists
|
|
|
|
/// purely so that host objects can be constructed by the VM.
|
|
|
|
fn construct(
|
|
|
|
&self,
|
|
|
|
avm: &mut Avm2<'gc>,
|
|
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
|
|
args: &[Value<'gc>],
|
|
|
|
) -> Result<Object<'gc>, Error>;
|
|
|
|
|
2020-02-06 04:15:03 +00:00
|
|
|
/// Get a raw pointer value for this object.
|
|
|
|
fn as_ptr(&self) -> *const ObjectPtr;
|
2020-02-24 03:11:02 +00:00
|
|
|
|
|
|
|
/// Get this object's `Executable`, if it has one.
|
|
|
|
fn as_executable(&self) -> Option<Executable<'gc>> {
|
|
|
|
None
|
|
|
|
}
|
2020-02-06 04:15:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum ObjectPtr {}
|
|
|
|
|
|
|
|
impl<'gc> Object<'gc> {
|
|
|
|
pub fn ptr_eq(a: Object<'gc>, b: Object<'gc>) -> bool {
|
|
|
|
a.as_ptr() == b.as_ptr()
|
|
|
|
}
|
2020-02-05 18:52:11 +00:00
|
|
|
}
|