diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 452247aa2..c5360e8bd 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1384,7 +1384,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> { fn op_get_slot(&mut self, index: u32) -> Result, 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, 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) } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 2415f0394..7c9856033 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -207,11 +207,49 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + 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, Error>; + #[allow(unused_mut)] + fn get_slot( + mut self, + activation: &mut Activation<'_, 'gc, '_>, + id: u32, + ) -> 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.get_slot_local(id) + } + + /// Retrieve a slot by its index, ignoring uninstalled traits. + fn get_slot_local(self, id: u32) -> Result, 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> + 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>, diff --git a/core/src/avm2/object/custom_object.rs b/core/src/avm2/object/custom_object.rs index 4147f5461..07e2aedd6 100644 --- a/core/src/avm2/object/custom_object.rs +++ b/core/src/avm2/object/custom_object.rs @@ -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, 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, 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> { diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 64f2a88b8..279de26d7 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -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, 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, 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> { @@ -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, 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, 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, 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>, diff --git a/core/src/avm2/object/stage_object.rs b/core/src/avm2/object/stage_object.rs index a04be5dd2..520331708 100644 --- a/core/src/avm2/object/stage_object.rs +++ b/core/src/avm2/object/stage_object.rs @@ -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, 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, 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> { diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index de6b061be..3321aab54 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -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()), } } diff --git a/core/src/avm2/slot.rs b/core/src/avm2/slot.rs index 3007bdaea..844fdecb1 100644 --- a/core/src/avm2/slot.rs +++ b/core/src/avm2/slot.rs @@ -86,4 +86,8 @@ impl<'gc> Slot<'gc> { } } } + + pub fn is_occupied(&self) -> bool { + matches!(self, Slot::Occupied { .. }) + } }