avm2: Instantiate slot traits on `get_slot`, `set_slot` etc.

Previously, we only instantiated slot traits when named as a property, which is only half the picture.
This commit is contained in:
David Wendt 2021-04-07 18:53:34 -04:00 committed by Mike Welsh
parent e35d1edbf0
commit fd08a6ebf6
7 changed files with 117 additions and 31 deletions

View File

@ -1384,7 +1384,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn op_get_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> {
let object = self.context.avm2.pop().coerce_to_object(self)?;
let value = object.get_slot(index)?;
let value = object.get_slot(self, index)?;
self.context.avm2.push(value);
@ -1395,13 +1395,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let value = self.context.avm2.pop();
let object = self.context.avm2.pop().coerce_to_object(self)?;
object.set_slot(index, value, self.context.gc_context)?;
object.set_slot(self, index, value)?;
Ok(FrameControl::Continue)
}
fn op_get_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error> {
let value = self.scope.unwrap().read().globals().get_slot(index)?;
let value = self.scope.unwrap().read().globals().get_slot(self, index)?;
self.context.avm2.push(value);
@ -1415,7 +1415,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.unwrap()
.read()
.globals()
.set_slot(index, value, self.context.gc_context)?;
.set_slot(self, index, value)?;
Ok(FrameControl::Continue)
}

View File

@ -207,11 +207,49 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
receiver.init_property_local(receiver, name, value, activation)
}
/// Determines if a local slot already exists and is occupied.
fn has_slot_local(self, id: u32) -> bool;
/// Retrieve a slot by its index.
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error>;
#[allow(unused_mut)]
fn get_slot(
mut self,
activation: &mut Activation<'_, 'gc, '_>,
id: u32,
) -> Result<Value<'gc>, Error> {
if !self.has_slot_local(id) {
let slot_trait = self
.get_trait_slot(id)?
.ok_or_else(|| format!("Slot index {} out of bounds!", id))?;
self.install_trait(activation, slot_trait, self.into())?;
}
self.get_slot_local(id)
}
/// Retrieve a slot by its index, ignoring uninstalled traits.
fn get_slot_local(self, id: u32) -> Result<Value<'gc>, Error>;
/// Set a slot by its index.
#[allow(unused_mut)]
fn set_slot(
mut self,
activation: &mut Activation<'_, 'gc, '_>,
id: u32,
value: Value<'gc>,
) -> Result<(), Error> {
if !self.has_slot_local(id) {
let slot_trait = self
.get_trait_slot(id)?
.ok_or_else(|| format!("Slot index {} out of bounds!", id))?;
self.install_trait(activation, slot_trait, self.into())?;
}
self.set_slot_local(id, value, activation.context.gc_context)
}
/// Set a slot by its index, ignoring uninstalled traits.
fn set_slot_local(
self,
id: u32,
value: Value<'gc>,
@ -219,7 +257,25 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) -> Result<(), Error>;
/// Initialize a slot by its index.
#[allow(unused_mut)]
fn init_slot(
mut self,
activation: &mut Activation<'_, 'gc, '_>,
id: u32,
value: Value<'gc>,
) -> Result<(), Error> {
if !self.has_slot_local(id) {
let slot_trait = self
.get_trait_slot(id)?
.ok_or_else(|| format!("Slot index {} out of bounds!", id))?;
self.install_trait(activation, slot_trait, self.into())?;
}
self.init_slot_local(id, value, activation.context.gc_context)
}
/// Initialize a slot by its index, ignoring uninstalled traits.
fn init_slot_local(
self,
id: u32,
value: Value<'gc>,

View File

@ -94,26 +94,30 @@ macro_rules! impl_avm2_custom_object_properties {
#[macro_export]
macro_rules! impl_avm2_custom_object {
($field:ident) => {
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error> {
self.0.read().$field.get_slot(id)
fn has_slot_local(self, id: u32) -> bool {
self.0.read().$field.has_slot_local(id)
}
fn set_slot(
fn get_slot_local(self, id: u32) -> Result<Value<'gc>, Error> {
self.0.read().$field.get_slot_local(id)
}
fn set_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).$field.set_slot(id, value, mc)
self.0.write(mc).$field.set_slot_local(id, value, mc)
}
fn init_slot(
fn init_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).$field.init_slot(id, value, mc)
self.0.write(mc).$field.init_slot_local(id, value, mc)
}
fn get_method(self, id: u32) -> Option<Object<'gc>> {

View File

@ -134,26 +134,30 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
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 has_slot_local(self, id: u32) -> bool {
self.0.read().has_slot_local(id)
}
fn set_slot(
fn get_slot_local(self, id: u32) -> Result<Value<'gc>, Error> {
self.0.read().get_slot_local(id)
}
fn set_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).set_slot(id, value, mc)
self.0.write(mc).set_slot_local(id, value, mc)
}
fn init_slot(
fn init_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).init_slot(id, value, mc)
self.0.write(mc).init_slot_local(id, value, mc)
}
fn get_method(self, id: u32) -> Option<Object<'gc>> {
@ -467,7 +471,9 @@ impl<'gc> ScriptObjectData<'gc> {
};
if let Some(slot_id) = slot_id {
self.set_slot(slot_id, value, activation.context.gc_context)?;
// This doesn't need the non-local version of this property because
// by the time this has called the slot was already installed
self.set_slot_local(slot_id, value, activation.context.gc_context)?;
Ok(Value::Undefined.into())
} else if self.values.contains_key(name) {
let prop = self.values.get_mut(name).unwrap();
@ -492,7 +498,10 @@ impl<'gc> ScriptObjectData<'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, activation.context.gc_context)?;
// This doesn't need the non-local version of this property
// because by the time this has called the slot was already
// installed
self.init_slot_local(slot_id, value, activation.context.gc_context)?;
Ok(Value::Undefined.into())
} else {
let proto = self.proto;
@ -528,8 +537,14 @@ impl<'gc> ScriptObjectData<'gc> {
can_delete
}
pub fn get_slot(&self, id: u32) -> Result<Value<'gc>, Error> {
//TODO: slot inheritance, I think?
pub fn has_slot_local(&self, id: u32) -> bool {
self.slots
.get(id as usize)
.map(|s| s.is_occupied())
.unwrap_or(false)
}
pub fn get_slot_local(&self, id: u32) -> Result<Value<'gc>, Error> {
self.slots
.get(id as usize)
.cloned()
@ -538,7 +553,7 @@ impl<'gc> ScriptObjectData<'gc> {
}
/// Set a slot by its index.
pub fn set_slot(
pub fn set_slot_local(
&mut self,
id: u32,
value: Value<'gc>,
@ -551,8 +566,8 @@ impl<'gc> ScriptObjectData<'gc> {
}
}
/// Set a slot by its index.
pub fn init_slot(
/// Initialize a slot by its index.
pub fn init_slot_local(
&mut self,
id: u32,
value: Value<'gc>,

View File

@ -135,26 +135,30 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.write(gc_context).base.delete_property(multiname)
}
fn get_slot(self, id: u32) -> Result<Value<'gc>, Error> {
self.0.read().base.get_slot(id)
fn has_slot_local(self, id: u32) -> bool {
self.0.read().base.has_slot_local(id)
}
fn set_slot(
fn get_slot_local(self, id: u32) -> Result<Value<'gc>, Error> {
self.0.read().base.get_slot_local(id)
}
fn set_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).base.set_slot(id, value, mc)
self.0.write(mc).base.set_slot_local(id, value, mc)
}
fn init_slot(
fn init_slot_local(
self,
id: u32,
value: Value<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<(), Error> {
self.0.write(mc).base.init_slot(id, value, mc)
self.0.write(mc).base.init_slot_local(id, value, mc)
}
fn get_method(self, id: u32) -> Option<Object<'gc>> {

View File

@ -142,7 +142,10 @@ impl<'gc> Property<'gc> {
)),
Property::Virtual { get: None, .. } => Ok(Value::Undefined.into()),
Property::Stored { value, .. } => Ok(value.to_owned().into()),
Property::Slot { slot_id, .. } => this.get_slot(*slot_id).map(|v| v.into()),
// This doesn't need the non-local version of this property because
// by the time this has called the slot was already installed
Property::Slot { slot_id, .. } => this.get_slot_local(*slot_id).map(|v| v.into()),
}
}

View File

@ -86,4 +86,8 @@ impl<'gc> Slot<'gc> {
}
}
}
pub fn is_occupied(&self) -> bool {
matches!(self, Slot::Occupied { .. })
}
}