Implement constant slots and traits.

Class and Function traits now generate const slots, too.
This commit is contained in:
David Wendt 2020-02-20 23:20:46 -05:00
parent 412c3d8489
commit fd275bdcf3
6 changed files with 161 additions and 9 deletions

View File

@ -36,6 +36,7 @@ mod property;
mod return_value;
mod scope;
mod script_object;
mod slot;
mod value;
/// Boxed error alias.

View File

@ -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)
}
}

View File

@ -155,6 +155,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + 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<Object<'gc>> + 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<Object<'gc>> + 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(())

View File

@ -61,6 +61,14 @@ impl<'gc> Property<'gc> {
}
}
/// Create a new stored property.
pub fn new_const(value: impl Into<Value<'gc>>) -> 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<Value<'gc>>) -> Self {
Property::Stored {

View File

@ -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<QName, Property<'gc>>,
/// Slots stored on this object.
slots: Vec<Value<'gc>>,
slots: Vec<Slot<'gc>>,
/// Implicit prototype (or declared base class) of this script object.
proto: Option<Object<'gc>>,
@ -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<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.
@ -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);
}
}
}

82
core/src/avm2/slot.rs Normal file
View File

@ -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<Attribute>,
},
}
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<Value<'gc>>) -> Self {
Self::Occupied {
value: value.into(),
attributes: EnumSet::empty(),
}
}
/// Create a `const` slot that cannot be overwritten.
pub fn new_const(value: impl Into<Value<'gc>>) -> Self {
Self::Occupied {
value: value.into(),
attributes: EnumSet::from(Attribute::ReadOnly),
}
}
/// Retrieve the value of this slot.
pub fn get(&self) -> Option<Value<'gc>> {
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<Value<'gc>>) -> 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(())
}
}
}
}