From fd275bdcf3910d2d52b89b79cad345bdeef45dcc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Thu, 20 Feb 2020 23:20:46 -0500 Subject: [PATCH] Implement constant slots and traits. Class and Function traits now generate const slots, too. --- core/src/avm2.rs | 1 + core/src/avm2/function.rs | 10 +++++ core/src/avm2/object.rs | 22 +++++++-- core/src/avm2/property.rs | 8 ++++ core/src/avm2/script_object.rs | 47 ++++++++++++++++--- core/src/avm2/slot.rs | 82 ++++++++++++++++++++++++++++++++++ 6 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 core/src/avm2/slot.rs diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 7916dac52..5b54696c9 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -36,6 +36,7 @@ mod property; mod return_value; mod scope; mod script_object; +mod slot; mod value; /// Boxed error alias. diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index f9684f339..756d28645 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -479,4 +479,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { ) { self.0.write(mc).base.install_slot(name, id, value) } + + 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) + } } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 39e61a433..d3d1683b5 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -155,6 +155,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy value: Value<'gc>, ); + /// Install a const on an object property. + fn install_const( + &mut self, + mc: MutationContext<'gc, '_>, + name: QName, + id: u32, + value: Value<'gc>, + ); + /// Install a trait from an ABC file on an object. fn install_trait( &mut self, @@ -250,7 +259,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy &type_entry.abc(), type_entry.instance().name.clone(), )?; - self.install_slot(context.gc_context, class_name, *slot_id, class.into()); + self.install_const(context.gc_context, class_name, *slot_id, class.into()); } AbcTraitKind::Function { slot_id, function, .. @@ -258,9 +267,16 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); let function = FunctionObject::from_abc_method(context.gc_context, method, scope, fn_proto); - self.install_slot(context.gc_context, trait_name, *slot_id, function.into()); + 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); } - _ => return Err("".into()), } Ok(()) diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 246f683c7..eb6298b6b 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -61,6 +61,14 @@ impl<'gc> Property<'gc> { } } + /// Create a new stored property. + pub fn new_const(value: impl Into>) -> Self { + Property::Stored { + value: value.into(), + attributes: Attribute::ReadOnly | Attribute::DontDelete, + } + } + /// Convert a value into a dynamic property. pub fn new_dynamic_property(value: impl Into>) -> Self { Property::Stored { diff --git a/core/src/avm2/script_object.rs b/core/src/avm2/script_object.rs index 9f674bdfe..fb5434811 100644 --- a/core/src/avm2/script_object.rs +++ b/core/src/avm2/script_object.rs @@ -5,6 +5,7 @@ use crate::avm2::names::QName; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::return_value::ReturnValue; +use crate::avm2::slot::Slot; use crate::avm2::value::Value; use crate::avm2::{Avm2, Error}; use crate::context::UpdateContext; @@ -24,7 +25,7 @@ pub struct ScriptObjectData<'gc> { values: HashMap>, /// Slots stored on this object. - slots: Vec>, + slots: Vec>, /// Implicit prototype (or declared base class) of this script object. proto: Option>, @@ -127,6 +128,16 @@ impl<'gc> TObject<'gc> for ScriptObject<'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> { @@ -193,10 +204,12 @@ impl<'gc> ScriptObjectData<'gc> { } pub fn get_slot(&self, id: u32) -> Result, 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. @@ -207,9 +220,7 @@ impl<'gc> ScriptObjectData<'gc> { _mc: MutationContext<'gc, '_>, ) -> Result<(), Error> { if let Some(slot) = self.slots.get_mut(id as usize) { - *slot = value; - - Ok(()) + slot.set(value) } else { Err(format!("Slot index {} out of bounds!", id).into()) } @@ -281,8 +292,32 @@ impl<'gc> ScriptObjectData<'gc> { self.values.insert(name, Property::new_stored(value)); } else { self.values.insert(name, Property::new_slot(id)); - if self.slots.len() < id as usize { - self.slots.resize(id as usize, Value::Undefined); + 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); } } } diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs new file mode 100644 index 000000000..105589357 --- /dev/null +++ b/core/src/avm2/slot.rs @@ -0,0 +1,82 @@ +//! Slot contents type + +use crate::avm2::property::Attribute; +use crate::avm2::value::Value; +use crate::avm2::Error; +use enumset::EnumSet; +use gc_arena::{Collect, CollectionContext}; + +/// Represents a single slot on an object. +#[derive(Clone, Debug)] +pub enum Slot<'gc> { + /// An unoccupied slot. + /// + /// Attempts to read an unoccupied slot proceed up the prototype chain. + /// Writing an unoccupied slot will always fail. + Unoccupied, + + /// An occupied slot. + Occupied { + value: Value<'gc>, + attributes: EnumSet, + }, +} + +unsafe impl<'gc> Collect for Slot<'gc> { + fn trace(&self, cc: CollectionContext) { + match self { + Self::Unoccupied => {} + Self::Occupied { value, .. } => value.trace(cc), + } + } +} + +impl<'gc> Default for Slot<'gc> { + fn default() -> Self { + Self::Unoccupied + } +} + +impl<'gc> Slot<'gc> { + /// Create a normal slot with a given value. + pub fn new(value: impl Into>) -> Self { + Self::Occupied { + value: value.into(), + attributes: EnumSet::empty(), + } + } + + /// Create a `const` slot that cannot be overwritten. + pub fn new_const(value: impl Into>) -> Self { + Self::Occupied { + value: value.into(), + attributes: EnumSet::from(Attribute::ReadOnly), + } + } + + /// Retrieve the value of this slot. + pub fn get(&self) -> Option> { + match self { + Self::Unoccupied => None, + Self::Occupied { value, .. } => Some(value.clone()), + } + } + + /// Write the value of this slot. + pub fn set(&mut self, new_value: impl Into>) -> Result<(), Error> { + match self { + Self::Unoccupied => Err("Cannot overwrite unoccupied slot".into()), + Self::Occupied { value, attributes } => { + if attributes.contains(Attribute::ReadOnly) { + return Err("Cannot overwrite const slot".into()); + } + + //TODO: Type assert + + *value = new_value.into(); + + Ok(()) + } + } + } +}