avm2: Make ScriptObjectData.vtable non-Optional

This commit is contained in:
Lord-McSweeney 2024-08-07 12:34:32 -07:00 committed by Lord-McSweeney
parent fa4df53831
commit 863e621bbb
9 changed files with 92 additions and 91 deletions

View File

@ -202,7 +202,7 @@ pub fn recursive_serialize<'gc>(
object_table: &mut ObjectTable<'gc>,
) -> Result<(), Error<'gc>> {
if let Some(static_properties) = static_properties {
if let Some(vtable) = obj.vtable() {
let vtable = obj.vtable();
let mut props = vtable.public_properties();
// Flash appears to use vtable iteration order, but we sort ours
// to make our test output consistent.
@ -215,19 +215,14 @@ pub fn recursive_serialize<'gc>(
}
let value = obj.get_public_property(name, activation)?;
let name = name.to_utf8_lossy().to_string();
if let Some(elem) = get_or_create_element(
activation,
name.clone(),
value,
object_table,
amf_version,
) {
if let Some(elem) =
get_or_create_element(activation, name.clone(), value, object_table, amf_version)
{
elements.push(elem);
static_properties.push(name);
}
}
}
}
// FIXME: Flash only seems to use this enumeration for dynamic classes.
let mut last_index = obj.get_next_enumerant(0, activation)?;

View File

@ -514,23 +514,35 @@ pub fn load_player_globals<'gc>(
domain.export_class(class_i_class.name(), class_i_class, mc);
domain.export_class(fn_classdef.name(), fn_classdef, mc);
// Initialize the global object. This gives it a temporary vtable until the
// global ClassObject is constructed and we have the true vtable.
let globals = ScriptObject::custom_object(mc, global_classdef, None, global_classdef.vtable());
// Initialize the script
let globals = ScriptObject::custom_object(mc, global_classdef, None, None);
let script = Script::empty_script(mc, globals, domain);
let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]);
activation.set_outer(gs);
let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?;
let object_proto = ScriptObject::custom_object(mc, object_i_class, Some(object_class), None);
let object_proto =
ScriptObject::custom_object(mc, object_i_class, None, object_class.instance_vtable());
let class_class =
ClassObject::from_class_partial(activation, class_i_class, Some(object_class))?;
let class_proto =
ScriptObject::custom_object(mc, object_i_class, Some(object_class), Some(object_proto));
let class_proto = ScriptObject::custom_object(
mc,
object_i_class,
Some(object_proto),
object_class.instance_vtable(),
);
let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?;
let fn_proto = ScriptObject::custom_object(mc, fn_classdef, Some(fn_class), Some(object_proto));
let fn_proto = ScriptObject::custom_object(
mc,
fn_classdef,
Some(object_proto),
fn_class.instance_vtable(),
);
// Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?;

View File

@ -241,7 +241,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
multiname: &Multiname<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
match self.vtable().get_trait(multiname) {
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
self.base().get_slot(slot_id)
}
@ -260,7 +260,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
let bound_method = self
.vtable()
.expect("object to have a vtable")
.make_bound_method(activation, self.into(), disp_id)
.ok_or_else(|| format!("Method not found with id {disp_id}"))?;
@ -345,11 +344,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
value: Value<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
match self.vtable().get_trait(multiname) {
Some(Property::Slot { slot_id }) => {
let value = self
.vtable()
.unwrap()
.coerce_trait_value(slot_id, value, activation)?;
self.base()
.set_slot(slot_id, value, activation.context.gc_context)
@ -429,11 +427,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
value: Value<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
match self.vtable().get_trait(multiname) {
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
let value = self
.vtable()
.unwrap()
.coerce_trait_value(slot_id, value, activation)?;
self.base()
.set_slot(slot_id, value, activation.context.gc_context)
@ -498,7 +495,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
arguments: &[Value<'gc>],
activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
match self.vtable().get_trait(multiname) {
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
let obj = self.base().get_slot(slot_id)?.as_callable(
activation,
@ -563,10 +560,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
value: Value<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
let value = self
.vtable()
.unwrap()
.coerce_trait_value(id, value, activation)?;
let value = self.vtable().coerce_trait_value(id, value, activation)?;
let base = self.base();
base.set_slot(id, value, activation.gc())
@ -600,7 +594,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
let full_method = self
.vtable()
.expect("method to have a vtable")
.get_full_method(id)
.ok_or_else(|| format!("Cannot call unknown method id {id}"))?;
@ -724,7 +717,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
));
}
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
match self.vtable().get_trait(multiname) {
None => {
if self.instance_class().is_sealed() {
Ok(false)
@ -878,7 +871,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) {
let new_slot_id = self
.vtable()
.unwrap()
.install_const_trait_late(mc, name, value, class);
self.base().install_const_slot_late(mc, new_slot_id, value);
@ -1053,7 +1045,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&self,
activation: &mut Activation<'_, 'gc>,
) -> Result<Vec<(AvmString<'gc>, Value<'gc>)>, Error<'gc>> {
if let Some(vtable) = self.vtable() {
let vtable = self.vtable();
let mut values = Vec::new();
for (name, prop) in vtable.public_properties() {
match prop {
@ -1066,10 +1059,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
_ => {}
}
}
Ok(values)
} else {
Ok(Vec::new())
}
}
/// Determine if this object has a given prototype in its prototype chain.
@ -1111,7 +1102,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get this object's vtable, if it has one.
/// Every object with class should have a vtable
#[no_dynamic]
fn vtable(&self) -> Option<VTable<'gc>> {
fn vtable(&self) -> VTable<'gc> {
let base = self.base();
base.vtable()
}

View File

@ -173,7 +173,10 @@ impl<'gc> ClassObject<'gc> {
let class_object = ClassObject(Gc::new(
activation.context.gc_context,
ClassObjectData {
base: ScriptObjectData::custom_new(c_class, None, None),
// We pass `custom_new` the temporary vtable of the class object
// because we don't have the full vtable created yet. We'll
// set it to the true vtable in `into_finished_class`.
base: ScriptObjectData::custom_new(c_class, None, c_class.vtable()),
class,
prototype: Lock::new(None),
class_scope: scope,

View File

@ -204,19 +204,21 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
activation: &mut Activation<'_, 'gc>,
arguments: &[Value<'gc>],
) -> Result<Object<'gc>, Error<'gc>> {
let object_class = activation.avm2().classes().object;
let prototype = if let Some(proto) = self.prototype() {
proto
} else {
let proto = activation.avm2().classes().object.prototype();
let proto = object_class.prototype();
self.set_prototype(Some(proto), activation.gc());
proto
};
let instance = ScriptObject::custom_object(
activation.context.gc_context,
activation.avm2().classes().object.inner_class_definition(),
Some(activation.avm2().classes().object),
object_class.inner_class_definition(),
Some(prototype),
object_class.instance_vtable(),
);
self.call(instance.into(), arguments, activation)?;

View File

@ -62,7 +62,7 @@ pub struct ScriptObjectData<'gc> {
instance_class: Class<'gc>,
/// The table used for non-dynamic property lookups.
vtable: Lock<Option<VTable<'gc>>>,
vtable: Lock<VTable<'gc>>,
}
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
@ -106,12 +106,12 @@ impl<'gc> ScriptObject<'gc> {
pub fn custom_object(
mc: &Mutation<'gc>,
class: Class<'gc>,
class_obj: Option<ClassObject<'gc>>,
proto: Option<Object<'gc>>,
vtable: VTable<'gc>,
) -> Object<'gc> {
ScriptObject(Gc::new(
mc,
ScriptObjectData::custom_new(class, proto, class_obj),
ScriptObjectData::custom_new(class, proto, vtable),
))
.into()
}
@ -121,6 +121,8 @@ impl<'gc> ScriptObject<'gc> {
pub fn catch_scope(activation: &mut Activation<'_, 'gc>, qname: &QName<'gc>) -> Object<'gc> {
let mc = activation.context.gc_context;
let vt = VTable::newcatch(mc, qname);
// TODO: use a proper ClassObject here; purposefully crafted bytecode
// can observe (the lack of) it.
let base = ScriptObjectWrapper(Gc::new(
@ -128,12 +130,10 @@ impl<'gc> ScriptObject<'gc> {
ScriptObjectData::custom_new(
activation.avm2().classes().object.inner_class_definition(),
None,
None,
vt,
),
));
let vt = VTable::newcatch(mc, qname);
base.set_vtable(mc, vt);
base.install_instance_slots(mc);
ScriptObject(base.0).into()
@ -147,7 +147,7 @@ impl<'gc> ScriptObjectData<'gc> {
Self::custom_new(
instance_of.inner_class_definition(),
Some(instance_of.prototype()),
Some(instance_of),
instance_of.instance_vtable(),
)
}
@ -159,7 +159,7 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn custom_new(
instance_class: Class<'gc>,
proto: Option<Object<'gc>>,
instance_of: Option<ClassObject<'gc>>,
vtable: VTable<'gc>,
) -> Self {
ScriptObjectData {
values: RefLock::new(Default::default()),
@ -167,7 +167,7 @@ impl<'gc> ScriptObjectData<'gc> {
bound_methods: RefLock::new(Vec::new()),
proto: Lock::new(proto),
instance_class,
vtable: Lock::new(instance_of.map(|cls| cls.instance_vtable())),
vtable: Lock::new(vtable),
}
}
}
@ -342,7 +342,7 @@ impl<'gc> ScriptObjectWrapper<'gc> {
pub fn install_instance_slots(&self, mc: &Mutation<'gc>) {
use std::ops::Deref;
let vtable = self.vtable().unwrap();
let vtable = self.vtable();
let default_slots = vtable.default_slots();
let mut slots = self.slots_mut(mc);
@ -374,16 +374,9 @@ impl<'gc> ScriptObjectWrapper<'gc> {
}
pub fn has_trait(&self, name: &Multiname<'gc>) -> bool {
match self.vtable() {
// Class instances have instance traits from any class in the base
// class chain.
Some(vtable) => vtable.has_trait(name),
// bare objects do not have traits.
// TODO: should we have bare objects at all?
// Shouldn't every object have a vtable?
None => false,
}
self.vtable().has_trait(name)
}
pub fn has_own_dynamic_property(&self, name: &Multiname<'gc>) -> bool {
@ -464,7 +457,7 @@ impl<'gc> ScriptObjectWrapper<'gc> {
}
/// Get the vtable for this object, if it has one.
pub fn vtable(&self) -> Option<VTable<'gc>> {
pub fn vtable(&self) -> VTable<'gc> {
self.0.vtable.get()
}
@ -473,7 +466,13 @@ impl<'gc> ScriptObjectWrapper<'gc> {
}
pub fn set_vtable(&self, mc: &Mutation<'gc>, vtable: VTable<'gc>) {
unlock!(Gc::write(mc, self.0), ScriptObjectData, vtable).set(Some(vtable));
// Make sure both vtables have the same number of slots
assert_eq!(
self.vtable().default_slots().len(),
vtable.default_slots().len()
);
unlock!(Gc::write(mc, self.0), ScriptObjectData, vtable).set(vtable);
}
pub fn debug_class_name(&self) -> Box<dyn std::fmt::Debug + 'gc> {

View File

@ -181,11 +181,10 @@ impl<'gc> ScopeChain<'gc> {
// NOTE: We are manually searching the vtable's traits so we can figure out which namespace the trait
// belongs to.
let values = scope.values();
if let Some(vtable) = values.vtable() {
let vtable = values.vtable();
if let Some((namespace, _)) = vtable.get_trait_with_ns(multiname) {
return Ok(Some((Some(namespace), values)));
}
}
// Wasn't in the objects traits, let's try dynamic properties if this is a with scope.
if scope.with() && values.has_own_property(multiname) {

View File

@ -639,7 +639,7 @@ impl<'gc> Script<'gc> {
let scope = ScopeChain::new(domain);
globals.vtable().unwrap().init_vtable(
globals.vtable().init_vtable(
globals.instance_class(),
self.0.read().global_class_obj,
&self.traits()?,

View File

@ -288,11 +288,11 @@ impl Avm2ObjectWindow {
) {
let mut entries = Vec::<(String, Namespace<'gc>, Property)>::new();
// We can't access things whilst we iterate the vtable, so clone and sort it all here
if let Some(vtable) = object.vtable() {
let vtable = object.vtable();
for (name, ns, prop) in vtable.resolved_traits().iter() {
entries.push((name.to_string(), ns, *prop));
}
}
entries.sort_by(|a, b| a.0.cmp(&b.0));
ui.horizontal(|ui| {