avm2: Make ScriptObjectData.vtable non-Optional
This commit is contained in:
parent
fa4df53831
commit
863e621bbb
|
@ -202,30 +202,25 @@ 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 mut props = vtable.public_properties();
|
||||
// Flash appears to use vtable iteration order, but we sort ours
|
||||
// to make our test output consistent.
|
||||
props.sort_by_key(|(name, _)| name.to_utf8_lossy().to_string());
|
||||
for (name, prop) in props {
|
||||
if let Property::Virtual { get, set } = prop {
|
||||
if !(get.is_some() && set.is_some()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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,
|
||||
) {
|
||||
elements.push(elem);
|
||||
static_properties.push(name);
|
||||
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.
|
||||
props.sort_by_key(|(name, _)| name.to_utf8_lossy().to_string());
|
||||
for (name, prop) in props {
|
||||
if let Property::Virtual { get, set } = prop {
|
||||
if !(get.is_some() && set.is_some()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
elements.push(elem);
|
||||
static_properties.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,23 +1045,22 @@ 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 mut values = Vec::new();
|
||||
for (name, prop) in vtable.public_properties() {
|
||||
match prop {
|
||||
Property::Slot { slot_id } | Property::ConstSlot { slot_id } => {
|
||||
values.push((name, self.base().get_slot(slot_id)?));
|
||||
}
|
||||
Property::Virtual { get: Some(get), .. } => {
|
||||
values.push((name, self.call_method(get, &[], activation)?))
|
||||
}
|
||||
_ => {}
|
||||
let vtable = self.vtable();
|
||||
|
||||
let mut values = Vec::new();
|
||||
for (name, prop) in vtable.public_properties() {
|
||||
match prop {
|
||||
Property::Slot { slot_id } | Property::ConstSlot { slot_id } => {
|
||||
values.push((name, self.base().get_slot(slot_id)?));
|
||||
}
|
||||
Property::Virtual { get: Some(get), .. } => {
|
||||
values.push((name, self.call_method(get, &[], activation)?))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(values)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
// Class instances have instance traits from any class in the base
|
||||
// class chain.
|
||||
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> {
|
||||
|
|
|
@ -181,10 +181,9 @@ 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() {
|
||||
if let Some((namespace, _)) = vtable.get_trait_with_ns(multiname) {
|
||||
return Ok(Some((Some(namespace), values)));
|
||||
}
|
||||
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.
|
||||
|
|
|
@ -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()?,
|
||||
|
|
|
@ -288,10 +288,10 @@ 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() {
|
||||
for (name, ns, prop) in vtable.resolved_traits().iter() {
|
||||
entries.push((name.to_string(), ns, *prop));
|
||||
}
|
||||
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));
|
||||
|
||||
|
|
Loading…
Reference in New Issue