Skip to content

Commit 2ce6679

Browse files
authored
avm2: Store PropertyClass in VtableData
Calling `get_trait` copies the returned `Property`, so the caching we performed in `PropertyClass` was never actually getting used. Instead, we now store our `PropertyClass` values in a `Vec` indexed by slot id. `set_property` and `init_property` now perform coercions by going through the `VTable,` which writes the updated `PropertyClass` back into the array.
1 parent 01a0d70 commit 2ce6679

File tree

3 files changed

+90
-56
lines changed

3 files changed

+90
-56
lines changed

core/src/avm2/object.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
142142
activation: &mut Activation<'_, 'gc, '_>,
143143
) -> Result<Value<'gc>, Error> {
144144
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
145-
Some(Property::Slot { slot_id, class: _ })
146-
| Some(Property::ConstSlot { slot_id, class: _ }) => self.base().get_slot(slot_id),
145+
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
146+
self.base().get_slot(slot_id)
147+
}
147148
Some(Property::Method { disp_id }) => {
148149
if let Some(bound_method) = self.get_bound_method(disp_id) {
149150
return Ok(bound_method.into());
@@ -197,8 +198,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
197198
activation: &mut Activation<'_, 'gc, '_>,
198199
) -> Result<(), Error> {
199200
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
200-
Some(Property::Slot { slot_id, mut class }) => {
201-
let value = class.coerce(activation, value)?;
201+
Some(Property::Slot { slot_id }) => {
202+
let value = self
203+
.vtable()
204+
.unwrap()
205+
.coerce_trait_value(slot_id, value, activation)?;
202206
self.base_mut(activation.context.gc_context).set_slot(
203207
slot_id,
204208
value,
@@ -247,9 +251,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
247251
activation: &mut Activation<'_, 'gc, '_>,
248252
) -> Result<(), Error> {
249253
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
250-
Some(Property::Slot { slot_id, mut class })
251-
| Some(Property::ConstSlot { slot_id, mut class }) => {
252-
let value = class.coerce(activation, value)?;
254+
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
255+
let value = self
256+
.vtable()
257+
.unwrap()
258+
.coerce_trait_value(slot_id, value, activation)?;
253259
self.base_mut(activation.context.gc_context).set_slot(
254260
slot_id,
255261
value,
@@ -303,8 +309,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
303309
activation: &mut Activation<'_, 'gc, '_>,
304310
) -> Result<Value<'gc>, Error> {
305311
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
306-
Some(Property::Slot { slot_id, class: _ })
307-
| Some(Property::ConstSlot { slot_id, class: _ }) => {
312+
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
308313
let obj = self.base().get_slot(slot_id)?.as_callable(
309314
activation,
310315
Some(multiname),

core/src/avm2/property.rs

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,11 @@ use gc_arena::{Collect, Gc};
1111

1212
#[derive(Debug, Collect, Clone, Copy)]
1313
#[collect(no_drop)]
14-
pub enum Property<'gc> {
15-
Virtual {
16-
get: Option<u32>,
17-
set: Option<u32>,
18-
},
19-
Method {
20-
disp_id: u32,
21-
},
22-
Slot {
23-
slot_id: u32,
24-
class: PropertyClass<'gc>,
25-
},
26-
ConstSlot {
27-
slot_id: u32,
28-
class: PropertyClass<'gc>,
29-
},
14+
pub enum Property {
15+
Virtual { get: Option<u32>, set: Option<u32> },
16+
Method { disp_id: u32 },
17+
Slot { slot_id: u32 },
18+
ConstSlot { slot_id: u32 },
3019
}
3120

3221
/// The type of a `Slot`/`ConstSlot` property, represented
@@ -42,7 +31,7 @@ pub enum Property<'gc> {
4231
/// Additionally, property class resolution uses special
4332
/// logic, different from normal "runtime" class resolution,
4433
/// that allows private types to be referenced.
45-
#[derive(Debug, Collect, Clone, Copy)]
34+
#[derive(Debug, Collect, Clone)]
4635
#[collect(no_drop)]
4736
pub enum PropertyClass<'gc> {
4837
/// The type `*` (Multiname::is_any()). This allows
@@ -61,24 +50,28 @@ impl<'gc> PropertyClass<'gc> {
6150
) -> Self {
6251
PropertyClass::Name(Gc::allocate(activation.context.gc_context, (name, unit)))
6352
}
53+
54+
/// Returns `value` coerced to the type of this `PropertyClass`.
55+
/// The bool is `true` if this `PropertyClass` was just modified
56+
/// to cache a class resolution, and `false` if it was not modified.
6457
pub fn coerce(
6558
&mut self,
6659
activation: &mut Activation<'_, 'gc, '_>,
6760
value: Value<'gc>,
68-
) -> Result<Value<'gc>, Error> {
69-
let class = match self {
70-
PropertyClass::Class(class) => Some(*class),
61+
) -> Result<(Value<'gc>, bool), Error> {
62+
let (class, changed) = match self {
63+
PropertyClass::Class(class) => (Some(*class), false),
7164
PropertyClass::Name(gc) => {
7265
let (name, unit) = &**gc;
7366
let outcome = resolve_class_private(name, *unit, activation)?;
7467
let class = match outcome {
7568
ResolveOutcome::Class(class) => {
7669
*self = PropertyClass::Class(class);
77-
Some(class)
70+
(Some(class), true)
7871
}
7972
ResolveOutcome::Any => {
8073
*self = PropertyClass::Any;
81-
None
74+
(None, true)
8275
}
8376
ResolveOutcome::NotFound => {
8477
// FP allows a class to reference its own type in a static initializer:
@@ -103,7 +96,9 @@ impl<'gc> PropertyClass<'gc> {
10396
&& unit.map(|u| u.domain())
10497
== Some(class.class_scope().domain())
10598
{
106-
return Ok(value);
99+
// Even though resolution succeeded, we haven't modified
100+
// this `PropertyClass`, so return `false`
101+
return Ok((value, false));
107102
}
108103
}
109104
}
@@ -115,15 +110,15 @@ impl<'gc> PropertyClass<'gc> {
115110
};
116111
class
117112
}
118-
PropertyClass::Any => None,
113+
PropertyClass::Any => (None, false),
119114
};
120115

121116
if let Some(class) = class {
122-
value.coerce_to_type(activation, class)
117+
Ok((value.coerce_to_type(activation, class)?, changed))
123118
} else {
124119
// We have a type of '*' ("any"), so don't
125120
// perform any coercions
126-
Ok(value)
121+
Ok((value, changed))
127122
}
128123
}
129124
}
@@ -191,7 +186,7 @@ fn resolve_class_private<'gc>(
191186
}
192187
}
193188

194-
impl<'gc> Property<'gc> {
189+
impl Property {
195190
pub fn new_method(disp_id: u32) -> Self {
196191
Property::Method { disp_id }
197192
}
@@ -210,11 +205,11 @@ impl<'gc> Property<'gc> {
210205
}
211206
}
212207

213-
pub fn new_slot(slot_id: u32, class: PropertyClass<'gc>) -> Self {
214-
Property::Slot { slot_id, class }
208+
pub fn new_slot(slot_id: u32) -> Self {
209+
Property::Slot { slot_id }
215210
}
216211

217-
pub fn new_const_slot(slot_id: u32, class: PropertyClass<'gc>) -> Self {
218-
Property::ConstSlot { slot_id, class }
212+
pub fn new_const_slot(slot_id: u32) -> Self {
213+
Property::ConstSlot { slot_id }
219214
}
220215
}

core/src/avm2/vtable.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ pub struct VTableData<'gc> {
2727

2828
protected_namespace: Option<Namespace<'gc>>,
2929

30-
resolved_traits: PropertyMap<'gc, Property<'gc>>,
30+
resolved_traits: PropertyMap<'gc, Property>,
31+
32+
/// Stores the `PropertyClass` for each slot,
33+
/// indexed by `slot_id`
34+
slot_classes: Vec<PropertyClass<'gc>>,
3135

3236
method_table: Vec<ClassBoundMethod<'gc>>,
3337

@@ -54,6 +58,7 @@ impl<'gc> VTable<'gc> {
5458
scope: None,
5559
protected_namespace: None,
5660
resolved_traits: PropertyMap::new(),
61+
slot_classes: vec![],
5762
method_table: vec![],
5863
default_slots: vec![],
5964
},
@@ -64,14 +69,34 @@ impl<'gc> VTable<'gc> {
6469
VTable(GcCell::allocate(mc, self.0.read().clone()))
6570
}
6671

67-
pub fn get_trait(self, name: &Multiname<'gc>) -> Option<Property<'gc>> {
72+
pub fn get_trait(self, name: &Multiname<'gc>) -> Option<Property> {
6873
self.0
6974
.read()
7075
.resolved_traits
7176
.get_for_multiname(name)
7277
.cloned()
7378
}
7479

80+
/// Coerces `value` to the type of the slot with id `slot_id`
81+
pub fn coerce_trait_value(
82+
&self,
83+
slot_id: u32,
84+
value: Value<'gc>,
85+
activation: &mut Activation<'_, 'gc, '_>,
86+
) -> Result<Value<'gc>, Error> {
87+
// Drop the `write()` guard, as 'slot_class.coerce' may need to access this vtable.
88+
let mut slot_class = { self.0.read().slot_classes[slot_id as usize].clone() };
89+
90+
let (value, changed) = slot_class.coerce(activation, value)?;
91+
92+
// Calling coerce modified `PropertyClass` to cache the class lookup,
93+
// so store the new value back in the vtable.
94+
if changed {
95+
self.0.write(activation.context.gc_context).slot_classes[slot_id as usize] = slot_class;
96+
}
97+
Ok(value)
98+
}
99+
75100
pub fn has_trait(self, name: &Multiname<'gc>) -> bool {
76101
self.0
77102
.read()
@@ -175,6 +200,7 @@ impl<'gc> VTable<'gc> {
175200

176201
if let Some(superclass_vtable) = superclass_vtable {
177202
write.resolved_traits = superclass_vtable.0.read().resolved_traits.clone();
203+
write.slot_classes = superclass_vtable.0.read().slot_classes.clone();
178204
write.method_table = superclass_vtable.0.read().method_table.clone();
179205
write.default_slots = superclass_vtable.0.read().default_slots.clone();
180206

@@ -195,10 +221,11 @@ impl<'gc> VTable<'gc> {
195221
}
196222
}
197223

198-
let (resolved_traits, method_table, default_slots) = (
224+
let (resolved_traits, method_table, default_slots, slot_classes) = (
199225
&mut write.resolved_traits,
200226
&mut write.method_table,
201227
&mut write.default_slots,
228+
&mut write.slot_classes,
202229
);
203230

204231
for trait_data in traits {
@@ -299,31 +326,38 @@ impl<'gc> VTable<'gc> {
299326
slot_id
300327
};
301328

302-
let new_prop = match trait_data.kind() {
329+
if new_slot_id as usize >= slot_classes.len() {
330+
// We will overwrite `PropertyClass::Any` when we process the slots
331+
// with the ids that we just skipped over.
332+
slot_classes.resize(new_slot_id as usize + 1, PropertyClass::Any);
333+
}
334+
335+
let (new_prop, new_class) = match trait_data.kind() {
303336
TraitKind::Slot {
304337
type_name, unit, ..
305-
} => Property::new_slot(
306-
new_slot_id,
338+
} => (
339+
Property::new_slot(new_slot_id),
307340
PropertyClass::name(activation, type_name.clone(), *unit),
308341
),
309-
TraitKind::Function { .. } => Property::new_slot(
310-
new_slot_id,
342+
TraitKind::Function { .. } => (
343+
Property::new_slot(new_slot_id),
311344
PropertyClass::Class(activation.avm2().classes().function),
312345
),
313346
TraitKind::Const {
314347
type_name, unit, ..
315-
} => Property::new_const_slot(
316-
new_slot_id,
348+
} => (
349+
Property::new_const_slot(new_slot_id),
317350
PropertyClass::name(activation, type_name.clone(), *unit),
318351
),
319-
TraitKind::Class { .. } => Property::new_const_slot(
320-
new_slot_id,
352+
TraitKind::Class { .. } => (
353+
Property::new_const_slot(new_slot_id),
321354
PropertyClass::Class(activation.avm2().classes().class),
322355
),
323356
_ => unreachable!(),
324357
};
325358

326359
resolved_traits.insert(trait_data.name(), new_prop);
360+
slot_classes[new_slot_id as usize] = new_class;
327361
}
328362
}
329363
}
@@ -379,10 +413,10 @@ impl<'gc> VTable<'gc> {
379413

380414
write.default_slots.push(Some(value));
381415
let new_slot_id = write.default_slots.len() as u32 - 1;
382-
write.resolved_traits.insert(
383-
name,
384-
Property::new_slot(new_slot_id, PropertyClass::Class(class)),
385-
);
416+
write
417+
.resolved_traits
418+
.insert(name, Property::new_slot(new_slot_id));
419+
write.slot_classes.push(PropertyClass::Class(class));
386420

387421
new_slot_id
388422
}

0 commit comments

Comments
 (0)