959 lines
30 KiB
Rust
959 lines
30 KiB
Rust
//! Default AVM2 object impl
|
|
|
|
use crate::avm2::class::Avm2ClassEntry;
|
|
use crate::avm2::function::Executable;
|
|
use crate::avm2::names::{Namespace, QName};
|
|
use crate::avm2::object::{Object, ObjectPtr, TObject};
|
|
use crate::avm2::property::Property;
|
|
use crate::avm2::return_value::ReturnValue;
|
|
use crate::avm2::scope::Scope;
|
|
use crate::avm2::slot::Slot;
|
|
use crate::avm2::value::Value;
|
|
use crate::avm2::{Avm2, Error};
|
|
use crate::context::UpdateContext;
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
|
use std::collections::HashMap;
|
|
use std::fmt::Debug;
|
|
use std::rc::Rc;
|
|
use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind};
|
|
|
|
/// Default implementation of `avm2::Object`.
|
|
#[derive(Clone, Collect, Debug, Copy)]
|
|
#[collect(no_drop)]
|
|
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
|
|
|
/// Information necessary for a script object to have a class attached to it.
|
|
///
|
|
/// Classes can be attached to a `ScriptObject` such that the class's traits
|
|
/// are instantiated on-demand. Either class or instance traits can be
|
|
/// instantiated.
|
|
///
|
|
/// Trait instantiation obeys prototyping rules: prototypes provide their
|
|
/// instances with classes to pull traits from.
|
|
#[derive(Clone, Collect, Debug)]
|
|
#[collect(no_drop)]
|
|
pub enum ScriptObjectClass<'gc> {
|
|
/// Instantiate instance traits, for prototypes.
|
|
InstancePrototype(Avm2ClassEntry, Option<GcCell<'gc, Scope<'gc>>>),
|
|
|
|
/// Instantiate class traits, for class constructors.
|
|
ClassConstructor(Avm2ClassEntry, Option<GcCell<'gc, Scope<'gc>>>),
|
|
|
|
/// Do not instantiate any class or instance traits.
|
|
NoClass,
|
|
}
|
|
|
|
/// Base data common to all `TObject` implementations.
|
|
///
|
|
/// Host implementations of `TObject` should embed `ScriptObjectData` and
|
|
/// forward any trait method implementations it does not overwrite to this
|
|
/// struct.
|
|
#[derive(Clone, Collect, Debug)]
|
|
#[collect(no_drop)]
|
|
pub struct ScriptObjectData<'gc> {
|
|
/// Properties stored on this object.
|
|
values: HashMap<QName, Property<'gc>>,
|
|
|
|
/// Slots stored on this object.
|
|
slots: Vec<Slot<'gc>>,
|
|
|
|
/// Methods stored on this object.
|
|
methods: Vec<Option<Object<'gc>>>,
|
|
|
|
/// Implicit prototype of this script object.
|
|
proto: Option<Object<'gc>>,
|
|
|
|
/// The class that this script object represents.
|
|
class: ScriptObjectClass<'gc>,
|
|
|
|
/// Enumeratable property names.
|
|
enumerants: Vec<QName>,
|
|
}
|
|
|
|
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
|
|
fn get_property_local(
|
|
self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<Value<'gc>, Error> {
|
|
self.0
|
|
.read()
|
|
.get_property_local(reciever, name, avm, context)
|
|
}
|
|
|
|
fn set_property_local(
|
|
self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
value: Value<'gc>,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
let rv = self
|
|
.0
|
|
.write(context.gc_context)
|
|
.set_property_local(reciever, name, value, avm, context)?;
|
|
|
|
rv.resolve(avm, context)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn init_property_local(
|
|
self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
value: Value<'gc>,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
let rv = self
|
|
.0
|
|
.write(context.gc_context)
|
|
.init_property_local(reciever, name, value, avm, context)?;
|
|
|
|
rv.resolve(avm, context)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn is_property_overwritable(self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool {
|
|
self.0.write(gc_context).is_property_overwritable(name)
|
|
}
|
|
|
|
fn delete_property(&self, gc_context: MutationContext<'gc, '_>, name: &QName) -> bool {
|
|
self.0.write(gc_context).delete_property(name)
|
|
}
|
|
|
|
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error> {
|
|
self.0.read().get_slot(id)
|
|
}
|
|
|
|
fn set_slot(
|
|
self,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
mc: MutationContext<'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
self.0.write(mc).set_slot(id, value, mc)
|
|
}
|
|
|
|
fn init_slot(
|
|
self,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
mc: MutationContext<'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
self.0.write(mc).init_slot(id, value, mc)
|
|
}
|
|
|
|
fn get_method(self, id: u32) -> Option<Object<'gc>> {
|
|
self.0.read().get_method(id)
|
|
}
|
|
|
|
fn get_trait(self, name: &QName) -> Result<Vec<AbcTrait>, Error> {
|
|
self.0.read().get_trait(name)
|
|
}
|
|
|
|
fn get_provided_trait(
|
|
&self,
|
|
name: &QName,
|
|
known_traits: &mut Vec<AbcTrait>,
|
|
) -> Result<(), Error> {
|
|
self.0.read().get_provided_trait(name, known_traits)
|
|
}
|
|
|
|
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
self.0.read().get_scope()
|
|
}
|
|
|
|
fn get_abc(self) -> Option<Rc<AbcFile>> {
|
|
self.0.read().get_abc()
|
|
}
|
|
|
|
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
|
self.0.read().resolve_any(local_name)
|
|
}
|
|
|
|
fn resolve_any_trait(self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
|
self.0.read().resolve_any_trait(local_name)
|
|
}
|
|
|
|
fn has_own_property(self, name: &QName) -> Result<bool, Error> {
|
|
self.0.read().has_own_property(name)
|
|
}
|
|
|
|
fn has_trait(self, name: &QName) -> Result<bool, Error> {
|
|
self.0.read().has_trait(name)
|
|
}
|
|
|
|
fn provides_trait(self, name: &QName) -> Result<bool, Error> {
|
|
self.0.read().provides_trait(name)
|
|
}
|
|
|
|
fn has_instantiated_property(self, name: &QName) -> bool {
|
|
self.0.read().has_instantiated_property(name)
|
|
}
|
|
|
|
fn has_own_virtual_getter(self, name: &QName) -> bool {
|
|
self.0.read().has_own_virtual_getter(name)
|
|
}
|
|
|
|
fn has_own_virtual_setter(self, name: &QName) -> bool {
|
|
self.0.read().has_own_virtual_setter(name)
|
|
}
|
|
|
|
fn proto(&self) -> Option<Object<'gc>> {
|
|
self.0.read().proto
|
|
}
|
|
|
|
fn get_enumerant_name(&self, index: u32) -> Option<QName> {
|
|
self.0.read().get_enumerant_name(index)
|
|
}
|
|
|
|
fn property_is_enumerable(&self, name: &QName) -> bool {
|
|
self.0.read().property_is_enumerable(name)
|
|
}
|
|
|
|
fn set_local_property_is_enumerable(
|
|
&self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: &QName,
|
|
is_enumerable: bool,
|
|
) -> Result<(), Error> {
|
|
self.0
|
|
.write(mc)
|
|
.set_local_property_is_enumerable(name, is_enumerable)
|
|
}
|
|
|
|
fn as_ptr(&self) -> *const ObjectPtr {
|
|
self.0.as_ptr() as *const ObjectPtr
|
|
}
|
|
|
|
fn construct(
|
|
&self,
|
|
_avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
_args: &[Value<'gc>],
|
|
) -> Result<Object<'gc>, Error> {
|
|
let this: Object<'gc> = Object::ScriptObject(*self);
|
|
Ok(ScriptObject::object(context.gc_context, this))
|
|
}
|
|
|
|
fn derive(
|
|
&self,
|
|
_avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
class: Avm2ClassEntry,
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
) -> Result<Object<'gc>, Error> {
|
|
let this: Object<'gc> = Object::ScriptObject(*self);
|
|
Ok(ScriptObject::prototype(
|
|
context.gc_context,
|
|
this,
|
|
class,
|
|
scope,
|
|
))
|
|
}
|
|
|
|
fn to_string(&self) -> Result<Value<'gc>, Error> {
|
|
Ok("[object Object]".into())
|
|
}
|
|
|
|
fn value_of(&self) -> Result<Value<'gc>, Error> {
|
|
Ok(Value::Object(Object::from(*self)))
|
|
}
|
|
|
|
fn install_method(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
disp_id: u32,
|
|
function: Object<'gc>,
|
|
) {
|
|
self.0.write(mc).install_method(name, disp_id, function)
|
|
}
|
|
|
|
fn install_getter(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
disp_id: u32,
|
|
function: Object<'gc>,
|
|
) -> Result<(), Error> {
|
|
self.0.write(mc).install_getter(name, disp_id, function)
|
|
}
|
|
|
|
fn install_setter(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
disp_id: u32,
|
|
function: Object<'gc>,
|
|
) -> Result<(), Error> {
|
|
self.0.write(mc).install_setter(name, disp_id, function)
|
|
}
|
|
|
|
fn install_dynamic_property(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
value: Value<'gc>,
|
|
) -> Result<(), Error> {
|
|
self.0.write(mc).install_dynamic_property(name, value)
|
|
}
|
|
|
|
fn install_slot(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
) {
|
|
self.0.write(mc).install_slot(name, id, value)
|
|
}
|
|
|
|
fn install_const(
|
|
&mut self,
|
|
mc: MutationContext<'gc, '_>,
|
|
name: QName,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
) {
|
|
self.0.write(mc).install_const(name, id, value)
|
|
}
|
|
}
|
|
|
|
impl<'gc> ScriptObject<'gc> {
|
|
/// Construct a bare object with no base class.
|
|
///
|
|
/// This is *not* the same thing as an object literal, which actually does
|
|
/// have a base class: `Object`.
|
|
pub fn bare_object(mc: MutationContext<'gc, '_>) -> Object<'gc> {
|
|
ScriptObject(GcCell::allocate(
|
|
mc,
|
|
ScriptObjectData::base_new(None, ScriptObjectClass::NoClass),
|
|
))
|
|
.into()
|
|
}
|
|
|
|
/// Construct an object with a prototype.
|
|
pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
|
|
ScriptObject(GcCell::allocate(
|
|
mc,
|
|
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::NoClass),
|
|
))
|
|
.into()
|
|
}
|
|
|
|
/// Construct a prototype for an ES4 class.
|
|
pub fn prototype(
|
|
mc: MutationContext<'gc, '_>,
|
|
proto: Object<'gc>,
|
|
class: Avm2ClassEntry,
|
|
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
|
) -> Object<'gc> {
|
|
let script_class = ScriptObjectClass::InstancePrototype(class, scope);
|
|
|
|
ScriptObject(GcCell::allocate(
|
|
mc,
|
|
ScriptObjectData::base_new(Some(proto), script_class),
|
|
))
|
|
.into()
|
|
}
|
|
}
|
|
|
|
/// Given a list of traits from an ABC file, find the one that matches this
|
|
/// name.
|
|
///
|
|
/// This function adds it's result onto the list of known traits, with the
|
|
/// caveat that duplicate entries will be replaced (if allowed). As such, this
|
|
/// function should be run on the class hierarchy from top to bottom.
|
|
///
|
|
/// If a given trait has an invalid name, attempts to override a final trait,
|
|
/// or overlaps an existing trait without being an override, then this function
|
|
/// returns an error.
|
|
///
|
|
/// TODO: This is an O(n^2) algorithm, it sucks.
|
|
fn do_trait_lookup(
|
|
name: &QName,
|
|
known_traits: &mut Vec<AbcTrait>,
|
|
abc: Rc<AbcFile>,
|
|
traits: &[AbcTrait],
|
|
) -> Result<(), Error> {
|
|
for trait_entry in traits.iter() {
|
|
let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?;
|
|
|
|
if name == &trait_name {
|
|
for known_trait in known_traits.iter() {
|
|
match (&trait_entry.kind, &known_trait.kind) {
|
|
(AbcTraitKind::Getter { .. }, AbcTraitKind::Setter { .. }) => continue,
|
|
(AbcTraitKind::Setter { .. }, AbcTraitKind::Getter { .. }) => continue,
|
|
_ => {}
|
|
};
|
|
|
|
if known_trait.is_final {
|
|
return Err("Attempting to override a final definition".into());
|
|
}
|
|
|
|
if !trait_entry.is_override {
|
|
return Err("Definition override is not marked as override".into());
|
|
}
|
|
}
|
|
|
|
known_traits.push(trait_entry.clone());
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
impl<'gc> ScriptObjectData<'gc> {
|
|
pub fn base_new(proto: Option<Object<'gc>>, trait_source: ScriptObjectClass<'gc>) -> Self {
|
|
ScriptObjectData {
|
|
values: HashMap::new(),
|
|
slots: Vec::new(),
|
|
methods: Vec::new(),
|
|
proto,
|
|
class: trait_source,
|
|
enumerants: Vec::new(),
|
|
}
|
|
}
|
|
|
|
pub fn get_property_local(
|
|
&self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<Value<'gc>, Error> {
|
|
let prop = self.values.get(name);
|
|
|
|
if let Some(prop) = prop {
|
|
prop.get(
|
|
avm,
|
|
context,
|
|
reciever,
|
|
avm.current_stack_frame()
|
|
.and_then(|sf| sf.read().base_proto())
|
|
.or(self.proto),
|
|
)?
|
|
.resolve(avm, context)
|
|
} else {
|
|
Ok(Value::Undefined)
|
|
}
|
|
}
|
|
|
|
pub fn set_property_local(
|
|
&mut self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
value: Value<'gc>,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
if let Some(prop) = self.values.get_mut(name) {
|
|
if let Some(slot_id) = prop.slot_id() {
|
|
self.set_slot(slot_id, value, context.gc_context)?;
|
|
Ok(Value::Undefined.into())
|
|
} else {
|
|
let proto = self.proto;
|
|
prop.set(
|
|
avm,
|
|
context,
|
|
reciever,
|
|
avm.current_stack_frame()
|
|
.and_then(|sf| sf.read().base_proto())
|
|
.or(proto),
|
|
value,
|
|
)
|
|
}
|
|
} else {
|
|
//TODO: Not all classes are dynamic like this
|
|
self.enumerants.push(name.clone());
|
|
self.values
|
|
.insert(name.clone(), Property::new_dynamic_property(value));
|
|
|
|
Ok(Value::Undefined.into())
|
|
}
|
|
}
|
|
|
|
pub fn init_property_local(
|
|
&mut self,
|
|
reciever: Object<'gc>,
|
|
name: &QName,
|
|
value: Value<'gc>,
|
|
avm: &mut Avm2<'gc>,
|
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
) -> Result<ReturnValue<'gc>, Error> {
|
|
if let Some(prop) = self.values.get_mut(name) {
|
|
if let Some(slot_id) = prop.slot_id() {
|
|
self.init_slot(slot_id, value, context.gc_context)?;
|
|
Ok(Value::Undefined.into())
|
|
} else {
|
|
let proto = self.proto;
|
|
prop.init(
|
|
avm,
|
|
context,
|
|
reciever,
|
|
avm.current_stack_frame()
|
|
.and_then(|sf| sf.read().base_proto())
|
|
.or(proto),
|
|
value,
|
|
)
|
|
}
|
|
} else {
|
|
//TODO: Not all classes are dynamic like this
|
|
self.values
|
|
.insert(name.clone(), Property::new_dynamic_property(value));
|
|
|
|
Ok(Value::Undefined.into())
|
|
}
|
|
}
|
|
|
|
pub fn is_property_overwritable(&self, name: &QName) -> bool {
|
|
self.values
|
|
.get(name)
|
|
.map(|p| p.is_overwritable())
|
|
.unwrap_or(true)
|
|
}
|
|
|
|
pub fn delete_property(&mut self, name: &QName) -> bool {
|
|
let can_delete = if let Some(prop) = self.values.get(name) {
|
|
prop.can_delete()
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if can_delete {
|
|
self.values.remove(name);
|
|
}
|
|
|
|
can_delete
|
|
}
|
|
|
|
pub fn get_slot(&self, id: u32) -> Result<Value<'gc>, Error> {
|
|
//TODO: slot inheritance, I think?
|
|
self.slots
|
|
.get(id as usize)
|
|
.cloned()
|
|
.ok_or_else(|| format!("Slot index {} out of bounds!", id).into())
|
|
.map(|slot| slot.get().unwrap_or(Value::Undefined))
|
|
}
|
|
|
|
/// Set a slot by it's index.
|
|
pub fn set_slot(
|
|
&mut self,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
_mc: MutationContext<'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
if let Some(slot) = self.slots.get_mut(id as usize) {
|
|
slot.set(value)
|
|
} else {
|
|
Err(format!("Slot index {} out of bounds!", id).into())
|
|
}
|
|
}
|
|
|
|
/// Set a slot by it's index.
|
|
pub fn init_slot(
|
|
&mut self,
|
|
id: u32,
|
|
value: Value<'gc>,
|
|
_mc: MutationContext<'gc, '_>,
|
|
) -> Result<(), Error> {
|
|
if let Some(slot) = self.slots.get_mut(id as usize) {
|
|
slot.init(value)
|
|
} else {
|
|
Err(format!("Slot index {} out of bounds!", id).into())
|
|
}
|
|
}
|
|
|
|
/// Retrieve a method from the method table.
|
|
pub fn get_method(&self, id: u32) -> Option<Object<'gc>> {
|
|
self.methods.get(id as usize).and_then(|v| *v)
|
|
}
|
|
|
|
pub fn get_trait(&self, name: &QName) -> Result<Vec<AbcTrait>, Error> {
|
|
match &self.class {
|
|
//Class constructors have local traits only.
|
|
ScriptObjectClass::ClassConstructor(..) => {
|
|
let mut known_traits = Vec::new();
|
|
self.get_provided_trait(name, &mut known_traits)?;
|
|
|
|
Ok(known_traits)
|
|
}
|
|
|
|
//Prototypes do not have traits available locally, but they provide
|
|
//traits instead.
|
|
ScriptObjectClass::InstancePrototype(..) => Ok(Vec::new()),
|
|
|
|
//Instances walk the prototype chain to build a list of known
|
|
//traits provided by the classes attached to those prototypes.
|
|
ScriptObjectClass::NoClass => {
|
|
let mut known_traits = Vec::new();
|
|
let mut chain = Vec::new();
|
|
let mut proto = self.proto();
|
|
|
|
while let Some(p) = proto {
|
|
chain.push(p);
|
|
proto = p.proto();
|
|
}
|
|
|
|
for proto in chain.iter().rev() {
|
|
proto.get_provided_trait(name, &mut known_traits)?;
|
|
}
|
|
|
|
Ok(known_traits)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_provided_trait(
|
|
&self,
|
|
name: &QName,
|
|
known_traits: &mut Vec<AbcTrait>,
|
|
) -> Result<(), Error> {
|
|
match &self.class {
|
|
ScriptObjectClass::ClassConstructor(class, ..) => {
|
|
do_trait_lookup(name, known_traits, class.abc(), &class.class().traits)
|
|
}
|
|
ScriptObjectClass::InstancePrototype(class, ..) => {
|
|
do_trait_lookup(name, known_traits, class.abc(), &class.instance().traits)
|
|
}
|
|
ScriptObjectClass::NoClass => Ok(()),
|
|
}
|
|
}
|
|
|
|
pub fn has_trait(&self, name: &QName) -> Result<bool, Error> {
|
|
match &self.class {
|
|
//Class constructors have local traits only.
|
|
ScriptObjectClass::ClassConstructor(..) => self.provides_trait(name),
|
|
|
|
//Prototypes do not have traits available locally, but we walk
|
|
//through them to find traits (see `provides_trait`)
|
|
ScriptObjectClass::InstancePrototype(..) => Ok(false),
|
|
|
|
//Instances walk the prototype chain to build a list of known
|
|
//traits provided by the classes attached to those prototypes.
|
|
ScriptObjectClass::NoClass => {
|
|
let mut proto = self.proto();
|
|
|
|
while let Some(p) = proto {
|
|
if p.provides_trait(name)? {
|
|
return Ok(true);
|
|
}
|
|
|
|
proto = p.proto();
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn provides_trait(&self, name: &QName) -> Result<bool, Error> {
|
|
match &self.class {
|
|
ScriptObjectClass::ClassConstructor(class, ..) => {
|
|
for trait_entry in class.class().traits.iter() {
|
|
let trait_name =
|
|
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
|
|
|
|
if name == &trait_name {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
ScriptObjectClass::InstancePrototype(class, ..) => {
|
|
for trait_entry in class.instance().traits.iter() {
|
|
let trait_name =
|
|
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
|
|
|
|
if name == &trait_name {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
ScriptObjectClass::NoClass => {}
|
|
};
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
|
|
match &self.class {
|
|
ScriptObjectClass::ClassConstructor(_class, scope) => *scope,
|
|
ScriptObjectClass::InstancePrototype(_class, scope) => *scope,
|
|
ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_scope()),
|
|
}
|
|
}
|
|
|
|
pub fn get_abc(&self) -> Option<Rc<AbcFile>> {
|
|
match &self.class {
|
|
ScriptObjectClass::ClassConstructor(class, ..) => Some(class.abc()),
|
|
ScriptObjectClass::InstancePrototype(class, ..) => Some(class.abc()),
|
|
ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_abc()),
|
|
}
|
|
}
|
|
|
|
pub fn resolve_any(&self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
|
for (key, _value) in self.values.iter() {
|
|
if key.local_name() == local_name {
|
|
return Ok(Some(key.namespace().clone()));
|
|
}
|
|
}
|
|
|
|
match self.class {
|
|
ScriptObjectClass::ClassConstructor(..) => self.resolve_any_trait(local_name),
|
|
ScriptObjectClass::NoClass => self.resolve_any_trait(local_name),
|
|
_ => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub fn resolve_any_trait(&self, local_name: &str) -> Result<Option<Namespace>, Error> {
|
|
if let Some(proto) = self.proto {
|
|
let proto_trait_name = proto.resolve_any_trait(local_name)?;
|
|
if let Some(ns) = proto_trait_name {
|
|
return Ok(Some(ns));
|
|
}
|
|
}
|
|
|
|
match &self.class {
|
|
ScriptObjectClass::ClassConstructor(class, ..) => {
|
|
for trait_entry in class.class().traits.iter() {
|
|
let trait_name =
|
|
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
|
|
|
|
if local_name == trait_name.local_name() {
|
|
return Ok(Some(trait_name.namespace().clone()));
|
|
}
|
|
}
|
|
}
|
|
ScriptObjectClass::InstancePrototype(class, ..) => {
|
|
for trait_entry in class.instance().traits.iter() {
|
|
let trait_name =
|
|
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
|
|
|
|
if local_name == trait_name.local_name() {
|
|
return Ok(Some(trait_name.namespace().clone()));
|
|
}
|
|
}
|
|
}
|
|
ScriptObjectClass::NoClass => {}
|
|
};
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
pub fn has_own_property(&self, name: &QName) -> Result<bool, Error> {
|
|
Ok(self.values.get(name).is_some() || self.has_trait(name)?)
|
|
}
|
|
|
|
pub fn has_instantiated_property(&self, name: &QName) -> bool {
|
|
self.values.get(name).is_some()
|
|
}
|
|
|
|
pub fn has_own_virtual_getter(&self, name: &QName) -> bool {
|
|
match self.values.get(name) {
|
|
Some(Property::Virtual { get: Some(_), .. }) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn has_own_virtual_setter(&self, name: &QName) -> bool {
|
|
match self.values.get(name) {
|
|
Some(Property::Virtual { set: Some(_), .. }) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn proto(&self) -> Option<Object<'gc>> {
|
|
self.proto
|
|
}
|
|
|
|
pub fn get_enumerant_name(&self, index: u32) -> Option<QName> {
|
|
// NOTE: AVM2 object enumeration is one of the weakest parts of an
|
|
// otherwise well-designed VM. Notably, because of the way they
|
|
// implemented `hasnext` and `hasnext2`, all enumerants start from ONE.
|
|
// Hence why we have to `checked_sub` here in case some miscompiled
|
|
// code doesn't check for the zero index, which is actually a failure
|
|
// sentinel.
|
|
let true_index = (index as usize).checked_sub(1)?;
|
|
|
|
self.enumerants.get(true_index).cloned()
|
|
}
|
|
|
|
pub fn property_is_enumerable(&self, name: &QName) -> bool {
|
|
self.enumerants.contains(name)
|
|
}
|
|
|
|
pub fn set_local_property_is_enumerable(
|
|
&mut self,
|
|
name: &QName,
|
|
is_enumerable: bool,
|
|
) -> Result<(), Error> {
|
|
if is_enumerable && self.values.contains_key(name) && !self.enumerants.contains(name) {
|
|
// Traits are never enumerable
|
|
if self.has_trait(name)? {
|
|
return Ok(());
|
|
}
|
|
|
|
self.enumerants.push(name.clone());
|
|
} else if !is_enumerable && self.enumerants.contains(name) {
|
|
let mut index = None;
|
|
for (i, other_name) in self.enumerants.iter().enumerate() {
|
|
if other_name == name {
|
|
index = Some(i);
|
|
}
|
|
}
|
|
|
|
if let Some(index) = index {
|
|
self.enumerants.remove(index);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn class(&self) -> &ScriptObjectClass<'gc> {
|
|
&self.class
|
|
}
|
|
|
|
/// Install a method into the object.
|
|
pub fn install_method(&mut self, name: QName, disp_id: u32, function: Object<'gc>) {
|
|
if disp_id > 0 {
|
|
if self.methods.len() <= disp_id as usize {
|
|
self.methods
|
|
.resize_with(disp_id as usize + 1, Default::default);
|
|
}
|
|
|
|
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
|
|
}
|
|
|
|
self.values.insert(name, Property::new_method(function));
|
|
}
|
|
|
|
/// Install a getter into the object.
|
|
///
|
|
/// This is a little more complicated than methods, since virtual property
|
|
/// slots can be installed in two parts. Thus, we need to support
|
|
/// installing them in either order.
|
|
pub fn install_getter(
|
|
&mut self,
|
|
name: QName,
|
|
disp_id: u32,
|
|
function: Object<'gc>,
|
|
) -> Result<(), Error> {
|
|
let executable: Result<Executable<'gc>, Error> = function
|
|
.as_executable()
|
|
.ok_or_else(|| "Attempted to install getter without a valid method".into());
|
|
let executable = executable?;
|
|
|
|
if disp_id > 0 {
|
|
if self.methods.len() <= disp_id as usize {
|
|
self.methods
|
|
.resize_with(disp_id as usize + 1, Default::default);
|
|
}
|
|
|
|
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
|
|
}
|
|
|
|
if !self.values.contains_key(&name) {
|
|
self.values.insert(name.clone(), Property::new_virtual());
|
|
}
|
|
|
|
self.values
|
|
.get_mut(&name)
|
|
.unwrap()
|
|
.install_virtual_getter(executable)
|
|
}
|
|
|
|
/// Install a setter into the object.
|
|
///
|
|
/// This is a little more complicated than methods, since virtual property
|
|
/// slots can be installed in two parts. Thus, we need to support
|
|
/// installing them in either order.
|
|
pub fn install_setter(
|
|
&mut self,
|
|
name: QName,
|
|
disp_id: u32,
|
|
function: Object<'gc>,
|
|
) -> Result<(), Error> {
|
|
let executable: Result<Executable<'gc>, Error> = function
|
|
.as_executable()
|
|
.ok_or_else(|| "Attempted to install setter without a valid method".into());
|
|
let executable = executable?;
|
|
|
|
if disp_id > 0 {
|
|
if self.methods.len() <= disp_id as usize {
|
|
self.methods
|
|
.resize_with(disp_id as usize + 1, Default::default);
|
|
}
|
|
|
|
*self.methods.get_mut(disp_id as usize).unwrap() = Some(function);
|
|
}
|
|
|
|
if !self.values.contains_key(&name) {
|
|
self.values.insert(name.clone(), Property::new_virtual());
|
|
}
|
|
|
|
self.values
|
|
.get_mut(&name)
|
|
.unwrap()
|
|
.install_virtual_setter(executable)
|
|
}
|
|
|
|
pub fn install_dynamic_property(
|
|
&mut self,
|
|
name: QName,
|
|
value: Value<'gc>,
|
|
) -> Result<(), Error> {
|
|
self.values
|
|
.insert(name, Property::new_dynamic_property(value));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Install a slot onto the object.
|
|
///
|
|
/// Slot number zero indicates a slot ID that is unknown and should be
|
|
/// allocated by the VM - as far as I know, there is no way to discover
|
|
/// slot IDs, so we don't allocate a slot for them at all.
|
|
pub fn install_slot(&mut self, name: QName, id: u32, value: Value<'gc>) {
|
|
if id == 0 {
|
|
self.values.insert(name, Property::new_stored(value));
|
|
} else {
|
|
self.values.insert(name, Property::new_slot(id));
|
|
if self.slots.len() < id as usize + 1 {
|
|
self.slots.resize_with(id as usize + 1, Default::default);
|
|
}
|
|
|
|
if let Some(slot) = self.slots.get_mut(id as usize) {
|
|
*slot = Slot::new(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Install a const onto the object.
|
|
///
|
|
/// Slot number zero indicates a slot ID that is unknown and should be
|
|
/// allocated by the VM - as far as I know, there is no way to discover
|
|
/// slot IDs, so we don't allocate a slot for them at all.
|
|
pub fn install_const(&mut self, name: QName, id: u32, value: Value<'gc>) {
|
|
if id == 0 {
|
|
self.values.insert(name, Property::new_const(value));
|
|
} else {
|
|
self.values.insert(name, Property::new_slot(id));
|
|
if self.slots.len() < id as usize + 1 {
|
|
self.slots.resize_with(id as usize + 1, Default::default);
|
|
}
|
|
|
|
if let Some(slot) = self.slots.get_mut(id as usize) {
|
|
*slot = Slot::new_const(value);
|
|
}
|
|
}
|
|
}
|
|
}
|