avm2: Replace most uses of `instance_of` with `instance_class`

Also store a Class -> alias mapping instead of a ClassObject -> alias mapping for AMF.
This commit is contained in:
Lord-McSweeney 2024-06-09 09:46:06 -07:00 committed by Adrian Wielgosik
parent 2b17b6ce20
commit 2c9028d2f9
25 changed files with 342 additions and 279 deletions

View File

@ -72,6 +72,7 @@ mod vtable;
pub use crate::avm2::activation::Activation; pub use crate::avm2::activation::Activation;
pub use crate::avm2::array::ArrayStorage; pub use crate::avm2::array::ArrayStorage;
pub use crate::avm2::call_stack::{CallNode, CallStack}; pub use crate::avm2::call_stack::{CallNode, CallStack};
pub use crate::avm2::class::Class;
#[allow(unused)] // For debug_ui #[allow(unused)] // For debug_ui
pub use crate::avm2::domain::{Domain, DomainPtr}; pub use crate::avm2::domain::{Domain, DomainPtr};
pub use crate::avm2::error::Error; pub use crate::avm2::error::Error;
@ -179,7 +180,7 @@ pub struct Avm2<'gc> {
orphan_objects: Rc<Vec<DisplayObjectWeak<'gc>>>, orphan_objects: Rc<Vec<DisplayObjectWeak<'gc>>>,
alias_to_class_map: FnvHashMap<AvmString<'gc>, ClassObject<'gc>>, alias_to_class_map: FnvHashMap<AvmString<'gc>, ClassObject<'gc>>,
class_to_alias_map: FnvHashMap<ClassObject<'gc>, AvmString<'gc>>, class_to_alias_map: FnvHashMap<Class<'gc>, AvmString<'gc>>,
/// The api version of our root movie clip. Note - this is used as the /// The api version of our root movie clip. Note - this is used as the
/// api version for swfs loaded via `Loader`, overriding the api version /// api version for swfs loaded via `Loader`, overriding the api version
@ -293,14 +294,15 @@ impl<'gc> Avm2<'gc> {
pub fn register_class_alias(&mut self, name: AvmString<'gc>, class_object: ClassObject<'gc>) { pub fn register_class_alias(&mut self, name: AvmString<'gc>, class_object: ClassObject<'gc>) {
self.alias_to_class_map.insert(name, class_object); self.alias_to_class_map.insert(name, class_object);
self.class_to_alias_map.insert(class_object, name); self.class_to_alias_map
.insert(class_object.inner_class_definition(), name);
} }
pub fn get_class_by_alias(&self, name: AvmString<'gc>) -> Option<ClassObject<'gc>> { pub fn get_class_by_alias(&self, name: AvmString<'gc>) -> Option<ClassObject<'gc>> {
self.alias_to_class_map.get(&name).copied() self.alias_to_class_map.get(&name).copied()
} }
pub fn get_alias_by_class(&self, cls: ClassObject<'gc>) -> Option<AvmString<'gc>> { pub fn get_alias_by_class(&self, cls: Class<'gc>) -> Option<AvmString<'gc>> {
self.class_to_alias_map.get(&cls).copied() self.class_to_alias_map.get(&cls).copied()
} }

View File

@ -1632,6 +1632,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
fn op_get_outer_scope(&mut self, index: u32) -> Result<FrameControl<'gc>, Error<'gc>> { fn op_get_outer_scope(&mut self, index: u32) -> Result<FrameControl<'gc>, Error<'gc>> {
// Verifier ensures that this points to a valid outer scope // Verifier ensures that this points to a valid outer scope
let scope = self.outer.get_unchecked(index as usize); let scope = self.outer.get_unchecked(index as usize);
self.push_stack(scope.values()); self.push_stack(scope.values());
@ -1730,10 +1731,9 @@ impl<'a, 'gc> Activation<'a, 'gc> {
} else { } else {
// Even if it's an object with the "descendants" property, we won't support it. // Even if it's an object with the "descendants" property, we won't support it.
let class_name = object let class_name = object
.instance_of() .instance_class()
.map(|cls| { .map(|cls| {
cls.inner_class_definition() cls.name()
.name()
.to_qualified_name_err_message(self.context.gc_context) .to_qualified_name_err_message(self.context.gc_context)
}) })
.unwrap_or_else(|| AvmString::from("<UNKNOWN>")); .unwrap_or_else(|| AvmString::from("<UNKNOWN>"));

View File

@ -1,6 +1,7 @@
use std::rc::Rc; use std::rc::Rc;
use crate::avm2::bytearray::ByteArrayStorage; use crate::avm2::bytearray::ByteArrayStorage;
use crate::avm2::class::Class;
use crate::avm2::object::{ByteArrayObject, ClassObject, TObject, VectorObject}; use crate::avm2::object::{ByteArrayObject, ClassObject, TObject, VectorObject};
use crate::avm2::vector::VectorStorage; use crate::avm2::vector::VectorStorage;
use crate::avm2::ArrayObject; use crate::avm2::ArrayObject;
@ -72,7 +73,7 @@ pub fn serialize_value<'gc>(
Some(AmfValue::ECMAArray(dense, sparse, len)) Some(AmfValue::ECMAArray(dense, sparse, len))
} else if let Some(vec) = o.as_vector_storage() { } else if let Some(vec) = o.as_vector_storage() {
let val_type = vec.value_type(); let val_type = vec.value_type();
if val_type == Some(activation.avm2().classes().int) { if val_type == Some(activation.avm2().classes().int.inner_class_definition()) {
let int_vec: Vec<_> = vec let int_vec: Vec<_> = vec
.iter() .iter()
.map(|v| { .map(|v| {
@ -81,7 +82,9 @@ pub fn serialize_value<'gc>(
}) })
.collect(); .collect();
Some(AmfValue::VectorInt(int_vec, vec.is_fixed())) Some(AmfValue::VectorInt(int_vec, vec.is_fixed()))
} else if val_type == Some(activation.avm2().classes().uint) { } else if val_type
== Some(activation.avm2().classes().uint.inner_class_definition())
{
let uint_vec: Vec<_> = vec let uint_vec: Vec<_> = vec
.iter() .iter()
.map(|v| { .map(|v| {
@ -90,7 +93,9 @@ pub fn serialize_value<'gc>(
}) })
.collect(); .collect();
Some(AmfValue::VectorUInt(uint_vec, vec.is_fixed())) Some(AmfValue::VectorUInt(uint_vec, vec.is_fixed()))
} else if val_type == Some(activation.avm2().classes().number) { } else if val_type
== Some(activation.avm2().classes().number.inner_class_definition())
{
let num_vec: Vec<_> = vec let num_vec: Vec<_> = vec
.iter() .iter()
.map(|v| { .map(|v| {
@ -108,7 +113,8 @@ pub fn serialize_value<'gc>(
}) })
.collect(); .collect();
let val_type = val_type.unwrap_or(activation.avm2().classes().object); let val_type = val_type
.unwrap_or(activation.avm2().classes().object.inner_class_definition());
let name = class_to_alias(activation, val_type); let name = class_to_alias(activation, val_type);
Some(AmfValue::VectorObject(obj_vec, name, vec.is_fixed())) Some(AmfValue::VectorObject(obj_vec, name, vec.is_fixed()))
@ -125,11 +131,11 @@ pub fn serialize_value<'gc>(
} else if let Some(bytearray) = o.as_bytearray() { } else if let Some(bytearray) = o.as_bytearray() {
Some(AmfValue::ByteArray(bytearray.bytes().to_vec())) Some(AmfValue::ByteArray(bytearray.bytes().to_vec()))
} else { } else {
let class = o.instance_of().expect("Missing ClassObject"); let class = o.instance_class().expect("Missing Class");
let name = class_to_alias(activation, class); let name = class_to_alias(activation, class);
let mut attributes = EnumSet::empty(); let mut attributes = EnumSet::empty();
if !class.inner_class_definition().is_sealed() { if !class.is_sealed() {
attributes.insert(Attribute::Dynamic); attributes.insert(Attribute::Dynamic);
} }
@ -173,7 +179,7 @@ fn alias_to_class<'gc>(
} }
} }
fn class_to_alias<'gc>(activation: &mut Activation<'_, 'gc>, class: ClassObject<'gc>) -> String { fn class_to_alias<'gc>(activation: &mut Activation<'_, 'gc>, class: Class<'gc>) -> String {
if let Some(alias) = activation.avm2().get_alias_by_class(class) { if let Some(alias) = activation.avm2().get_alias_by_class(class) {
alias.to_string() alias.to_string()
} else { } else {
@ -359,7 +365,7 @@ pub fn deserialize_value<'gc>(
let storage = VectorStorage::from_values( let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(), vec.iter().map(|v| (*v).into()).collect(),
*is_fixed, *is_fixed,
Some(activation.avm2().classes().number), Some(activation.avm2().classes().number.inner_class_definition()),
); );
VectorObject::from_vector(storage, activation)?.into() VectorObject::from_vector(storage, activation)?.into()
} }
@ -367,7 +373,7 @@ pub fn deserialize_value<'gc>(
let storage = VectorStorage::from_values( let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(), vec.iter().map(|v| (*v).into()).collect(),
*is_fixed, *is_fixed,
Some(activation.avm2().classes().uint), Some(activation.avm2().classes().uint.inner_class_definition()),
); );
VectorObject::from_vector(storage, activation)?.into() VectorObject::from_vector(storage, activation)?.into()
} }
@ -375,7 +381,7 @@ pub fn deserialize_value<'gc>(
let storage = VectorStorage::from_values( let storage = VectorStorage::from_values(
vec.iter().map(|v| (*v).into()).collect(), vec.iter().map(|v| (*v).into()).collect(),
*is_fixed, *is_fixed,
Some(activation.avm2().classes().int), Some(activation.avm2().classes().int.inner_class_definition()),
); );
VectorObject::from_vector(storage, activation)?.into() VectorObject::from_vector(storage, activation)?.into()
} }
@ -397,7 +403,7 @@ pub fn deserialize_value<'gc>(
}) })
.collect::<Result<Vec<_>, _>>()?, .collect::<Result<Vec<_>, _>>()?,
*is_fixed, *is_fixed,
Some(class), Some(class.inner_class_definition()),
); );
VectorObject::from_vector(storage, activation)?.into() VectorObject::from_vector(storage, activation)?.into()
} }

View File

@ -104,6 +104,9 @@ pub struct ClassData<'gc> {
/// superinterfaces, nor interfaces implemented by the superclass. /// superinterfaces, nor interfaces implemented by the superclass.
direct_interfaces: Vec<Class<'gc>>, direct_interfaces: Vec<Class<'gc>>,
/// The list of all interfaces implemented by this class.
all_interfaces: Vec<Class<'gc>>,
/// The instance allocator for this class. /// The instance allocator for this class.
/// ///
/// If `None`, then instances of this object will be allocated the same way /// If `None`, then instances of this object will be allocated the same way
@ -163,7 +166,7 @@ pub struct ClassData<'gc> {
/// Maps a type parameter to the application of this class with that parameter. /// Maps a type parameter to the application of this class with that parameter.
/// ///
/// Only applicable if this class is generic. /// Only applicable if this class is generic.
applications: FnvHashMap<Option<ClassKey<'gc>>, Class<'gc>>, applications: FnvHashMap<Option<Class<'gc>>, Class<'gc>>,
/// Whether or not this is a system-defined class. /// Whether or not this is a system-defined class.
/// ///
@ -184,32 +187,20 @@ impl PartialEq for Class<'_> {
} }
} }
impl Eq for Class<'_> {}
impl Hash for Class<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_ptr().hash(state);
}
}
impl<'gc> core::fmt::Debug for Class<'gc> { impl<'gc> core::fmt::Debug for Class<'gc> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("Class").field("name", &self.name()).finish() f.debug_struct("Class").field("name", &self.name()).finish()
} }
} }
/// Allows using a `Class<'gc>` as a HashMap key,
/// using the pointer address for hashing/equality.
#[derive(Collect, Copy, Clone)]
#[collect(no_drop)]
struct ClassKey<'gc>(Class<'gc>);
impl PartialEq for ClassKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for ClassKey<'_> {}
impl Hash for ClassKey<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0 .0.as_ptr().hash(state);
}
}
impl<'gc> Class<'gc> { impl<'gc> Class<'gc> {
/// Create a new class. /// Create a new class.
/// ///
@ -237,6 +228,7 @@ impl<'gc> Class<'gc> {
attributes: ClassAttributes::empty(), attributes: ClassAttributes::empty(),
protected_namespace: None, protected_namespace: None,
direct_interfaces: Vec::new(), direct_interfaces: Vec::new(),
all_interfaces: Vec::new(),
instance_allocator: None, instance_allocator: None,
instance_init, instance_init,
native_instance_init, native_instance_init,
@ -255,8 +247,7 @@ impl<'gc> Class<'gc> {
} }
pub fn add_application(self, mc: &Mutation<'gc>, param: Option<Class<'gc>>, cls: Class<'gc>) { pub fn add_application(self, mc: &Mutation<'gc>, param: Option<Class<'gc>>, cls: Class<'gc>) {
let key = param.map(ClassKey); self.0.write(mc).applications.insert(param, cls);
self.0.write(mc).applications.insert(key, cls);
} }
/// Apply type parameters to an existing class. /// Apply type parameters to an existing class.
@ -271,9 +262,7 @@ impl<'gc> Class<'gc> {
let mc = context.gc_context; let mc = context.gc_context;
let this_read = this.0.read(); let this_read = this.0.read();
let key = param.map(ClassKey); if let Some(application) = this_read.applications.get(&param) {
if let Some(application) = this_read.applications.get(&key) {
return *application; return *application;
} }
@ -312,7 +301,7 @@ impl<'gc> Class<'gc> {
drop(this_read); drop(this_read);
this.0.write(mc).applications.insert(key, new_class); this.0.write(mc).applications.insert(Some(param), new_class);
new_class new_class
} }
@ -464,6 +453,7 @@ impl<'gc> Class<'gc> {
attributes, attributes,
protected_namespace, protected_namespace,
direct_interfaces: interfaces, direct_interfaces: interfaces,
all_interfaces: Vec::new(),
instance_allocator, instance_allocator,
instance_init, instance_init,
native_instance_init, native_instance_init,
@ -658,7 +648,7 @@ impl<'gc> Class<'gc> {
// interfaces (i.e. those that were not already implemented by the superclass) // interfaces (i.e. those that were not already implemented by the superclass)
// Otherwise, our behavior diverges from Flash Player in certain cases. // Otherwise, our behavior diverges from Flash Player in certain cases.
// See the ignored test 'tests/tests/swfs/avm2/weird_superinterface_properties/' // See the ignored test 'tests/tests/swfs/avm2/weird_superinterface_properties/'
for interface in interfaces { for interface in &interfaces {
for interface_trait in &*interface.instance_traits() { for interface_trait in &*interface.instance_traits() {
if !interface_trait.name().namespace().is_public() { if !interface_trait.name().namespace().is_public() {
let public_name = QName::new( let public_name = QName::new(
@ -674,6 +664,8 @@ impl<'gc> Class<'gc> {
} }
} }
self.0.write(context.gc_context).all_interfaces = interfaces;
Ok(()) Ok(())
} }
@ -704,6 +696,7 @@ impl<'gc> Class<'gc> {
attributes: ClassAttributes::empty(), attributes: ClassAttributes::empty(),
protected_namespace: None, protected_namespace: None,
direct_interfaces: Vec::new(), direct_interfaces: Vec::new(),
all_interfaces: Vec::new(),
instance_allocator: None, instance_allocator: None,
instance_init: Method::from_builtin( instance_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined), |_, _, _| Ok(Value::Undefined),
@ -737,6 +730,36 @@ impl<'gc> Class<'gc> {
Ok(class) Ok(class)
} }
/// Determine if this class has a given type in its superclass chain.
///
/// The given class `test_class` should be either a superclass or
/// interface we are checking against this class.
///
/// To test if a class *instance* is of a given type, see `Object::is_of_type`.
pub fn has_class_in_chain(self, test_class: Class<'gc>) -> bool {
let mut my_class = Some(self);
while let Some(class) = my_class {
if class == test_class {
return true;
}
my_class = class.super_class()
}
// A `Class` stores all of the interfaces it implements, including
// those from superinterfaces and superclasses (recursively).
if test_class.is_interface() {
for interface in &*self.all_interfaces() {
if *interface == test_class {
return true;
}
}
}
false
}
pub fn instance_vtable(self) -> VTable<'gc> { pub fn instance_vtable(self) -> VTable<'gc> {
self.0.read().instance_vtable self.0.read().instance_vtable
} }
@ -749,6 +772,26 @@ impl<'gc> Class<'gc> {
self.0.try_read().map(|r| r.name) self.0.try_read().map(|r| r.name)
} }
/// Attempts to obtain the name of this class.
/// If we are unable to read from the necessary `GcCell`,
/// the returned value will be some kind of error message.
///
/// This should only be used in a debug context, where
/// we need infallible access to *something* to print
/// out.
pub fn debug_name(self) -> Box<dyn fmt::Debug + 'gc> {
let class_name = self.try_name();
match class_name {
Ok(class_name) => Box::new(class_name),
Err(err) => Box::new(err),
}
}
pub fn param(self) -> Option<Option<Class<'gc>>> {
self.0.read().param
}
pub fn set_param(self, mc: &Mutation<'gc>, param: Option<Option<Class<'gc>>>) { pub fn set_param(self, mc: &Mutation<'gc>, param: Option<Option<Class<'gc>>>) {
self.0.write(mc).param = param; self.0.write(mc).param = param;
} }
@ -1079,6 +1122,10 @@ impl<'gc> Class<'gc> {
Ref::map(self.0.read(), |c| &c.direct_interfaces) Ref::map(self.0.read(), |c| &c.direct_interfaces)
} }
pub fn all_interfaces(&self) -> Ref<Vec<Class<'gc>>> {
Ref::map(self.0.read(), |c| &c.all_interfaces)
}
/// Determine if this class is sealed (no dynamic properties) /// Determine if this class is sealed (no dynamic properties)
pub fn is_sealed(self) -> bool { pub fn is_sealed(self) -> bool {
self.0.read().attributes.contains(ClassAttributes::SEALED) self.0.read().attributes.contains(ClassAttributes::SEALED)

View File

@ -1,10 +1,7 @@
use ruffle_wstr::WString; use ruffle_wstr::WString;
use crate::avm2::object::TObject; use crate::avm2::object::TObject;
use crate::avm2::Activation; use crate::avm2::{Activation, AvmString, Class, Multiname, Value};
use crate::avm2::AvmString;
use crate::avm2::Multiname;
use crate::avm2::Value;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Debug; use std::fmt::Debug;
use std::mem::size_of; use std::mem::size_of;
@ -94,13 +91,12 @@ pub fn make_reference_error<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
code: ReferenceErrorCode, code: ReferenceErrorCode,
multiname: &Multiname<'gc>, multiname: &Multiname<'gc>,
object_class: Option<ClassObject<'gc>>, object_class: Option<Class<'gc>>,
) -> Error<'gc> { ) -> Error<'gc> {
let qualified_name = multiname.as_uri(activation.context.gc_context); let qualified_name = multiname.as_uri(activation.context.gc_context);
let class_name = object_class let class_name = object_class
.map(|cls| { .map(|cls| {
cls.inner_class_definition() cls.name()
.name()
.to_qualified_name_err_message(activation.context.gc_context) .to_qualified_name_err_message(activation.context.gc_context)
}) })
.unwrap_or_else(|| AvmString::from("<UNKNOWN>")); .unwrap_or_else(|| AvmString::from("<UNKNOWN>"));

View File

@ -419,18 +419,17 @@ fn vector_class<'gc>(
let mc = activation.context.gc_context; let mc = activation.context.gc_context;
let (_, global, mut domain) = script.init(); let (_, global, mut domain) = script.init();
let cls = param_class.map(|c| c.inner_class_definition()); let param_class = param_class.map(|c| c.inner_class_definition());
let vector_cls = class( let vector_cls = class(
vector::create_builtin_class(activation, cls), vector::create_builtin_class(activation, param_class),
script, script,
activation, activation,
)?; )?;
vector_cls.set_param(mc, Some(param_class));
let generic_vector = activation.avm2().classes().generic_vector; let generic_vector = activation.avm2().classes().generic_vector;
generic_vector.add_application(mc, param_class, vector_cls); generic_vector.add_application(mc, param_class, vector_cls);
let generic_cls = generic_vector.inner_class_definition(); let generic_cls = generic_vector.inner_class_definition();
generic_cls.add_application(mc, cls, vector_cls.inner_class_definition()); generic_cls.add_application(mc, param_class, vector_cls.inner_class_definition());
let legacy_name = QName::new(activation.avm2().vector_internal_namespace, legacy_name); let legacy_name = QName::new(activation.avm2().vector_internal_namespace, legacy_name);
global.install_const_late( global.install_const_late(

View File

@ -92,7 +92,7 @@ pub fn init<'gc>(
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
// We set the underlying BitmapData instance - we start out with a dummy BitmapDataWrapper, // We set the underlying BitmapData instance - we start out with a dummy BitmapDataWrapper,
// which makes custom classes see a disposed BitmapData before they call super() // which makes custom classes see a disposed BitmapData before they call super()
let name = this.instance_of_class_definition().map(|c| c.name()); let name = this.instance_class().map(|c| c.name());
let character = this let character = this
.instance_of() .instance_of()
.and_then(|t| { .and_then(|t| {
@ -374,7 +374,7 @@ pub fn get_vector<'gc>(
height, height,
); );
let value_type = activation.avm2().classes().uint; let value_type = activation.avm2().classes().uint.inner_class_definition();
let new_storage = VectorStorage::from_values(pixels, false, Some(value_type)); let new_storage = VectorStorage::from_values(pixels, false, Some(value_type));
return Ok(VectorObject::from_vector(new_storage, activation)?.into()); return Ok(VectorObject::from_vector(new_storage, activation)?.into());

View File

@ -1166,7 +1166,11 @@ pub fn read_graphics_data<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData"); avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData");
let value_type = activation.avm2().classes().igraphicsdata; let value_type = activation
.avm2()
.classes()
.igraphicsdata
.inner_class_definition();
let new_storage = VectorStorage::new(0, false, Some(value_type), activation); let new_storage = VectorStorage::new(0, false, Some(value_type), activation);
Ok(VectorObject::from_vector(new_storage, activation)?.into()) Ok(VectorObject::from_vector(new_storage, activation)?.into())
} }
@ -1298,17 +1302,41 @@ fn handle_igraphics_data<'gc>(
drawing: &mut Drawing, drawing: &mut Drawing,
obj: &Object<'gc>, obj: &Object<'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
let class = obj.instance_of().expect("No class"); let class = obj.instance_class().expect("No class");
if class == activation.avm2().classes().graphicsbitmapfill { if class
== activation
.avm2()
.classes()
.graphicsbitmapfill
.inner_class_definition()
{
let style = handle_bitmap_fill(activation, drawing, obj)?; let style = handle_bitmap_fill(activation, drawing, obj)?;
drawing.set_fill_style(Some(style)); drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicsendfill { } else if class
== activation
.avm2()
.classes()
.graphicsendfill
.inner_class_definition()
{
drawing.set_fill_style(None); drawing.set_fill_style(None);
} else if class == activation.avm2().classes().graphicsgradientfill { } else if class
== activation
.avm2()
.classes()
.graphicsgradientfill
.inner_class_definition()
{
let style = handle_gradient_fill(activation, obj)?; let style = handle_gradient_fill(activation, obj)?;
drawing.set_fill_style(Some(style)); drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicspath { } else if class
== activation
.avm2()
.classes()
.graphicspath
.inner_class_definition()
{
let commands = obj let commands = obj
.get_public_property("commands", activation)? .get_public_property("commands", activation)?
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
@ -1330,13 +1358,31 @@ fn handle_igraphics_data<'gc>(
.expect("commands is not a Vector"), .expect("commands is not a Vector"),
&data.as_vector_storage().expect("data is not a Vector"), &data.as_vector_storage().expect("data is not a Vector"),
)?; )?;
} else if class == activation.avm2().classes().graphicssolidfill { } else if class
== activation
.avm2()
.classes()
.graphicssolidfill
.inner_class_definition()
{
let style = handle_solid_fill(activation, obj)?; let style = handle_solid_fill(activation, obj)?;
drawing.set_fill_style(Some(style)); drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicsshaderfill { } else if class
== activation
.avm2()
.classes()
.graphicsshaderfill
.inner_class_definition()
{
tracing::warn!("Graphics shader fill unimplemented {:?}", class); tracing::warn!("Graphics shader fill unimplemented {:?}", class);
drawing.set_fill_style(None); drawing.set_fill_style(None);
} else if class == activation.avm2().classes().graphicsstroke { } else if class
== activation
.avm2()
.classes()
.graphicsstroke
.inner_class_definition()
{
let thickness = obj let thickness = obj
.get_public_property("thickness", activation)? .get_public_property("thickness", activation)?
.coerce_to_number(activation)?; .coerce_to_number(activation)?;
@ -1392,7 +1438,13 @@ fn handle_igraphics_data<'gc>(
drawing.set_line_style(Some(line_style)); drawing.set_line_style(Some(line_style));
} }
} else if class == activation.avm2().classes().graphicstrianglepath { } else if class
== activation
.avm2()
.classes()
.graphicstrianglepath
.inner_class_definition()
{
handle_graphics_triangle_path(activation, drawing, obj)?; handle_graphics_triangle_path(activation, drawing, obj)?;
} else { } else {
panic!("Unknown graphics data class {:?}", class); panic!("Unknown graphics data class {:?}", class);
@ -1528,20 +1580,50 @@ fn handle_igraphics_fill<'gc>(
drawing: &mut Drawing, drawing: &mut Drawing,
obj: &Object<'gc>, obj: &Object<'gc>,
) -> Result<Option<FillStyle>, Error<'gc>> { ) -> Result<Option<FillStyle>, Error<'gc>> {
let class = obj.instance_of().expect("No class"); let class = obj.instance_class().expect("No class");
if class == activation.avm2().classes().graphicsbitmapfill { if class
== activation
.avm2()
.classes()
.graphicsbitmapfill
.inner_class_definition()
{
let style = handle_bitmap_fill(activation, drawing, obj)?; let style = handle_bitmap_fill(activation, drawing, obj)?;
Ok(Some(style)) Ok(Some(style))
} else if class == activation.avm2().classes().graphicsendfill { } else if class
== activation
.avm2()
.classes()
.graphicsendfill
.inner_class_definition()
{
Ok(None) Ok(None)
} else if class == activation.avm2().classes().graphicsgradientfill { } else if class
== activation
.avm2()
.classes()
.graphicsgradientfill
.inner_class_definition()
{
let style = handle_gradient_fill(activation, obj)?; let style = handle_gradient_fill(activation, obj)?;
Ok(Some(style)) Ok(Some(style))
} else if class == activation.avm2().classes().graphicssolidfill { } else if class
== activation
.avm2()
.classes()
.graphicssolidfill
.inner_class_definition()
{
let style = handle_solid_fill(activation, obj)?; let style = handle_solid_fill(activation, obj)?;
Ok(Some(style)) Ok(Some(style))
} else if class == activation.avm2().classes().graphicsshaderfill { } else if class
== activation
.avm2()
.classes()
.graphicsshaderfill
.inner_class_definition()
{
tracing::warn!("Graphics shader fill unimplemented {:?}", class); tracing::warn!("Graphics shader fill unimplemented {:?}", class);
Ok(None) Ok(None)
} else { } else {

View File

@ -423,7 +423,7 @@ pub fn get_stage3ds<'gc>(
.map(|obj| Value::Object(*obj)) .map(|obj| Value::Object(*obj))
.collect(), .collect(),
false, false,
Some(activation.avm2().classes().stage3d), Some(activation.avm2().classes().stage3d.inner_class_definition()),
); );
let stage3ds = VectorObject::from_vector(storage, activation)?; let stage3ds = VectorObject::from_vector(storage, activation)?;
return Ok(stage3ds.into()); return Ok(stage3ds.into());

View File

@ -68,7 +68,7 @@ pub fn attach_net_stream<'gc>(
return Err(format!( return Err(format!(
"Cannot use value of type {:?} as video source", "Cannot use value of type {:?} as video source",
source source
.and_then(|o| o.instance_of_class_definition()) .and_then(|o| o.instance_class())
.map(|c| c.name().local_name()) .map(|c| c.name().local_name())
.unwrap_or_else(|| "Object".into()) .unwrap_or_else(|| "Object".into())
) )

View File

@ -123,7 +123,7 @@ pub fn get_qualified_definition_names<'gc>(
.map(|name| Value::String(name.to_qualified_name(activation.context.gc_context))) .map(|name| Value::String(name.to_qualified_name(activation.context.gc_context)))
.collect(), .collect(),
false, false,
Some(activation.avm2().classes().string), Some(activation.avm2().classes().string.inner_class_definition()),
); );
let name_vector = VectorObject::from_vector(storage, activation)?; let name_vector = VectorObject::from_vector(storage, activation)?;

View File

@ -199,15 +199,14 @@ pub fn get_qualified_class_name<'gc>(
let obj = val.coerce_to_object(activation)?; let obj = val.coerce_to_object(activation)?;
let class = match obj.as_class_object() { let class = match obj.as_class_object() {
Some(class) => class, Some(class) => class.inner_class_definition(),
None => match obj.instance_of() { None => match obj.instance_class() {
Some(cls) => cls, Some(cls) => cls,
None => return Ok(Value::Null), None => return Ok(Value::Null),
}, },
}; };
Ok(class Ok(class
.inner_class_definition()
.name() .name()
.to_qualified_name(activation.context.gc_context) .to_qualified_name(activation.context.gc_context)
.into()) .into())
@ -225,16 +224,15 @@ pub fn get_qualified_superclass_name<'gc>(
.coerce_to_object(activation)?; .coerce_to_object(activation)?;
let class = match obj.as_class_object() { let class = match obj.as_class_object() {
Some(class) => class, Some(class) => class.inner_class_definition(),
None => match obj.instance_of() { None => match obj.instance_class() {
Some(cls) => cls, Some(cls) => cls,
None => return Ok(Value::Null), None => return Ok(Value::Null),
}, },
}; };
if let Some(super_class) = class.superclass_object() { if let Some(super_class) = class.super_class() {
Ok(super_class Ok(super_class
.inner_class_definition()
.name() .name()
.to_qualified_name(activation.context.gc_context) .to_qualified_name(activation.context.gc_context)
.into()) .into())

View File

@ -74,13 +74,14 @@ fn class_call<'gc>(
let this_class = activation.subclass_object().unwrap(); let this_class = activation.subclass_object().unwrap();
let value_type = this_class let value_type = this_class
.as_class_params() .inner_class_definition()
.param()
.ok_or("Cannot convert to unparametrized Vector")?; // technically unreachable .ok_or("Cannot convert to unparametrized Vector")?; // technically unreachable
let arg = args.get(0).cloned().unwrap(); let arg = args.get(0).cloned().unwrap();
let arg = arg.as_object().ok_or("Cannot convert to Vector")?; let arg = arg.as_object().ok_or("Cannot convert to Vector")?;
if arg.instance_of() == Some(this_class) { if arg.instance_class() == Some(this_class.inner_class_definition()) {
return Ok(arg.into()); return Ok(arg.into());
} }
@ -91,9 +92,7 @@ fn class_call<'gc>(
let mut new_storage = VectorStorage::new(0, false, value_type, activation); let mut new_storage = VectorStorage::new(0, false, value_type, activation);
new_storage.reserve_exact(length as usize); new_storage.reserve_exact(length as usize);
let value_type_for_coercion = new_storage let value_type_for_coercion = new_storage.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
let mut iter = ArrayIter::new(activation, arg)?; let mut iter = ArrayIter::new(activation, arg)?;
@ -248,9 +247,7 @@ pub fn concat<'gc>(
return Err("Not a vector-structured object".into()); return Err("Not a vector-structured object".into());
}; };
let val_class = new_vector_storage let val_class = new_vector_storage.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
for arg in args { for arg in args {
let arg_obj = arg let arg_obj = arg
@ -260,10 +257,10 @@ pub fn concat<'gc>(
// this is Vector.<int/uint/Number/*> // this is Vector.<int/uint/Number/*>
let my_base_vector_class = activation let my_base_vector_class = activation
.subclass_object() .subclass_object()
.expect("Method call without bound class?"); .expect("Method call without bound class?")
if !arg.is_of_type(activation, my_base_vector_class.inner_class_definition()) { .inner_class_definition();
if !arg.is_of_type(activation, my_base_vector_class) {
let base_vector_name = my_base_vector_class let base_vector_name = my_base_vector_class
.inner_class_definition()
.name() .name()
.to_qualified_name_err_message(activation.context.gc_context); .to_qualified_name_err_message(activation.context.gc_context);
@ -289,7 +286,7 @@ pub fn concat<'gc>(
if let Ok(val_obj) = val.coerce_to_object(activation) { if let Ok(val_obj) = val.coerce_to_object(activation) {
if !val.is_of_type(activation, val_class) { if !val.is_of_type(activation, val_class) {
let other_val_class = val_obj let other_val_class = val_obj
.instance_of_class_definition() .instance_class()
.ok_or("TypeError: Tried to concat a bare object into a Vector")?; .ok_or("TypeError: Tried to concat a bare object into a Vector")?;
return Err(format!( return Err(format!(
"TypeError: Cannot coerce Vector value of type {:?} to type {:?}", "TypeError: Cannot coerce Vector value of type {:?} to type {:?}",
@ -449,9 +446,9 @@ pub fn filter<'gc>(
let receiver = args.get(1).cloned().unwrap_or(Value::Null); let receiver = args.get(1).cloned().unwrap_or(Value::Null);
let value_type = this let value_type = this
.instance_of() .instance_class()
.unwrap() .unwrap()
.as_class_params() .param()
.ok_or("Cannot filter unparameterized vector")?; // technically unreachable .ok_or("Cannot filter unparameterized vector")?; // technically unreachable
let mut new_storage = VectorStorage::new(0, false, value_type, activation); let mut new_storage = VectorStorage::new(0, false, value_type, activation);
let mut iter = ArrayIter::new(activation, this)?; let mut iter = ArrayIter::new(activation, this)?;
@ -578,14 +575,12 @@ pub fn map<'gc>(
let receiver = args.get(1).cloned().unwrap_or(Value::Null); let receiver = args.get(1).cloned().unwrap_or(Value::Null);
let value_type = this let value_type = this
.instance_of() .instance_class()
.unwrap() .unwrap()
.as_class_params() .param()
.ok_or("Cannot filter unparameterized vector")?; // technically unreachable .ok_or("Cannot filter unparameterized vector")?; // technically unreachable
let mut new_storage = VectorStorage::new(0, false, value_type, activation); let mut new_storage = VectorStorage::new(0, false, value_type, activation);
let value_type_for_coercion = new_storage let value_type_for_coercion = new_storage.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
let mut iter = ArrayIter::new(activation, this)?; let mut iter = ArrayIter::new(activation, this)?;
while let Some(r) = iter.next(activation) { while let Some(r) = iter.next(activation) {
@ -620,9 +615,7 @@ pub fn push<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) { if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
let value_type = vs let value_type = vs.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
// Pushing nothing will still throw if the Vector is fixed. // Pushing nothing will still throw if the Vector is fixed.
vs.check_fixed(activation)?; vs.check_fixed(activation)?;
@ -659,9 +652,7 @@ pub fn unshift<'gc>(
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) { if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
let value_type = vs let value_type = vs.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
for arg in args.iter().rev() { for arg in args.iter().rev() {
let coerced_arg = arg.coerce_to_type(activation, value_type)?; let coerced_arg = arg.coerce_to_type(activation, value_type)?;
@ -687,9 +678,9 @@ pub fn insert_at<'gc>(
.cloned() .cloned()
.unwrap_or(Value::Undefined) .unwrap_or(Value::Undefined)
.coerce_to_i32(activation)?; .coerce_to_i32(activation)?;
let value_type = vs
.value_type_for_coercion(activation) let value_type = vs.value_type_for_coercion(activation);
.inner_class_definition();
let value = args let value = args
.get(1) .get(1)
.cloned() .cloned()
@ -877,9 +868,7 @@ pub fn splice<'gc>(
.unwrap_or(Value::Undefined) .unwrap_or(Value::Undefined)
.coerce_to_i32(activation)?; .coerce_to_i32(activation)?;
let value_type = vs.value_type(); let value_type = vs.value_type();
let value_type_for_coercion = vs let value_type_for_coercion = vs.value_type_for_coercion(activation);
.value_type_for_coercion(activation)
.inner_class_definition();
let start = vs.clamp_parameter_index(start_len); let start = vs.clamp_parameter_index(start_len);
let end = max( let end = max(

View File

@ -268,7 +268,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::ReadFromWriteOnly, error::ReferenceErrorCode::ReadFromWriteOnly,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
None => self.get_property_local(multiname, activation), None => self.get_property_local(multiname, activation),
@ -359,7 +359,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::AssignToMethod, error::ReferenceErrorCode::AssignToMethod,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
Some(Property::Virtual { set: Some(set), .. }) => { Some(Property::Virtual { set: Some(set), .. }) => {
@ -370,7 +370,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::WriteToReadOnly, error::ReferenceErrorCode::WriteToReadOnly,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
None => self.set_property_local(multiname, value, activation), None => self.set_property_local(multiname, value, activation),
@ -437,7 +437,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::AssignToMethod, error::ReferenceErrorCode::AssignToMethod,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
Some(Property::Virtual { set: Some(set), .. }) => { Some(Property::Virtual { set: Some(set), .. }) => {
@ -448,7 +448,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::WriteToReadOnly, error::ReferenceErrorCode::WriteToReadOnly,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
None => self.init_property_local(multiname, value, activation), None => self.init_property_local(multiname, value, activation),
@ -518,7 +518,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::ReadFromWriteOnly, error::ReferenceErrorCode::ReadFromWriteOnly,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
None => self.call_property_local(multiname, arguments, activation), None => self.call_property_local(multiname, arguments, activation),
@ -705,14 +705,14 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation, activation,
error::ReferenceErrorCode::InvalidDelete, error::ReferenceErrorCode::InvalidDelete,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) { match self.vtable().and_then(|vtable| vtable.get_trait(multiname)) {
None => { None => {
if self if self
.instance_of_class_definition() .instance_class()
.map(|c| c.is_sealed()) .map(|c| c.is_sealed())
.unwrap_or(false) .unwrap_or(false)
{ {
@ -968,7 +968,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// coercions. /// coercions.
fn to_string(&self, activation: &mut Activation<'_, 'gc>) -> Result<Value<'gc>, Error<'gc>> { fn to_string(&self, activation: &mut Activation<'_, 'gc>) -> Result<Value<'gc>, Error<'gc>> {
let class_name = self let class_name = self
.instance_of_class_definition() .instance_class()
.map(|c| c.name().local_name()) .map(|c| c.name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());
@ -988,7 +988,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let class_name = self let class_name = self
.instance_of_class_definition() .instance_class()
.map(|c| c.name().local_name()) .map(|c| c.name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());
@ -1086,7 +1086,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// The given object should be the class object for the given type we are /// The given object should be the class object for the given type we are
/// checking against this object. /// checking against this object.
fn is_of_type(&self, test_class: Class<'gc>, context: &mut UpdateContext<'_, 'gc>) -> bool { fn is_of_type(&self, test_class: Class<'gc>, context: &mut UpdateContext<'_, 'gc>) -> bool {
let my_class = self.instance_of(); let my_class = self.instance_class();
// ES3 objects are not class instances but are still treated as // ES3 objects are not class instances but are still treated as
// instances of Object, which is an ES4 class. // instances of Object, which is an ES4 class.
@ -1123,13 +1123,13 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
} }
/// Get this object's class's `Class`, if it has one. /// Get this object's class's `Class`, if it has one.
fn instance_of_class_definition(&self) -> Option<Class<'gc>> { fn instance_class(&self) -> Option<Class<'gc>> {
self.instance_of().map(|cls| cls.inner_class_definition()) self.instance_of().map(|cls| cls.inner_class_definition())
} }
/// Get this object's class's name, formatted for debug output. /// Get this object's class's name, formatted for debug output.
fn instance_of_class_name(&self, mc: &Mutation<'gc>) -> AvmString<'gc> { fn instance_of_class_name(&self, mc: &Mutation<'gc>) -> AvmString<'gc> {
self.instance_of_class_definition() self.instance_class()
.map(|r| r.name().to_qualified_name(mc)) .map(|r| r.name().to_qualified_name(mc))
.unwrap_or_else(|| "<Unknown type>".into()) .unwrap_or_else(|| "<Unknown type>".into())
} }

View File

@ -72,14 +72,6 @@ pub struct ClassObjectData<'gc> {
/// If None, a simple coercion is done. /// If None, a simple coercion is done.
call_handler: Option<Method<'gc>>, call_handler: Option<Method<'gc>>,
/// The parameters of this specialized class.
///
/// None flags that this class has not been specialized.
///
/// An individual parameter of `None` signifies the parameter `*`, which is
/// represented in AVM2 as `null` with regards to type application.
params: Option<Option<ClassObject<'gc>>>,
/// List of all applications of this class. /// List of all applications of this class.
/// ///
/// Only applicable if this class is generic. /// Only applicable if this class is generic.
@ -88,7 +80,7 @@ pub struct ClassObjectData<'gc> {
/// as `None` here. AVM2 considers both applications to be separate /// as `None` here. AVM2 considers both applications to be separate
/// classes, though we consider the parameter to be the class `Object` when /// classes, though we consider the parameter to be the class `Object` when
/// we get a param of `null`. /// we get a param of `null`.
applications: FnvHashMap<Option<ClassObject<'gc>>, ClassObject<'gc>>, applications: FnvHashMap<Option<Class<'gc>>, ClassObject<'gc>>,
/// Interfaces implemented by this class, including interfaces /// Interfaces implemented by this class, including interfaces
/// from parent classes and superinterfaces (recursively). /// from parent classes and superinterfaces (recursively).
@ -212,7 +204,6 @@ impl<'gc> ClassObject<'gc> {
constructor: class.instance_init(), constructor: class.instance_init(),
native_constructor: class.native_instance_init(), native_constructor: class.native_instance_init(),
call_handler: class.call_handler(), call_handler: class.call_handler(),
params: None,
applications: Default::default(), applications: Default::default(),
interfaces: Vec::new(), interfaces: Vec::new(),
instance_vtable: VTable::empty(activation.context.gc_context), instance_vtable: VTable::empty(activation.context.gc_context),
@ -429,39 +420,6 @@ impl<'gc> ClassObject<'gc> {
Ok(()) Ok(())
} }
/// Determine if this class has a given type in its superclass chain.
///
/// The given object `test_class` should be either a superclass or
/// interface we are checking against this class.
///
/// To test if a class *instance* is of a given type, see is_of_type.
pub fn has_class_in_chain(self, test_class: Class<'gc>) -> bool {
let mut my_class = Some(self);
while let Some(class) = my_class {
if class.inner_class_definition() == test_class {
return true;
}
my_class = class.superclass_object()
}
// A `ClassObject` stores all of the interfaces it implements,
// including those from superinterfaces and superclasses (recursively).
// Therefore, we only need to check interfaces once, and we can skip
// checking them when we processing superclasses in the `while`
// further down in this method.
if test_class.is_interface() {
for interface in self.interfaces() {
if interface == test_class {
return true;
}
}
}
false
}
/// Call the instance initializer. /// Call the instance initializer.
pub fn call_init( pub fn call_init(
self, self,
@ -722,12 +680,46 @@ impl<'gc> ClassObject<'gc> {
pub fn add_application( pub fn add_application(
&self, &self,
gc_context: &Mutation<'gc>, gc_context: &Mutation<'gc>,
param: Option<ClassObject<'gc>>, param: Option<Class<'gc>>,
cls: ClassObject<'gc>, cls: ClassObject<'gc>,
) { ) {
self.0.write(gc_context).applications.insert(param, cls); self.0.write(gc_context).applications.insert(param, cls);
} }
/// Parametrize this class. This does not check to ensure that this class is generic.
pub fn parametrize(
&self,
activation: &mut Activation<'_, 'gc>,
class_param: Option<Class<'gc>>,
) -> Result<ClassObject<'gc>, Error<'gc>> {
let self_class = self.inner_class_definition();
if let Some(application) = self.0.read().applications.get(&class_param) {
return Ok(*application);
}
// if it's not a known application, then it's not int/uint/Number/*,
// so it must be a simple Vector.<*>-derived class.
let parameterized_class =
Class::with_type_param(&mut activation.context, self_class, class_param);
// NOTE: this isn't fully accurate, but much simpler.
// FP's Vector is more of special case that literally copies some parent class's properties
// main example: Vector.<Object>.prototype === Vector.<*>.prototype
let vector_star_cls = activation.avm2().classes().object_vector;
let class_object =
Self::from_class(activation, parameterized_class, Some(vector_star_cls))?;
self.0
.write(activation.context.gc_context)
.applications
.insert(class_param, class_object);
Ok(class_object)
}
pub fn translation_unit(self) -> Option<TranslationUnit<'gc>> { pub fn translation_unit(self) -> Option<TranslationUnit<'gc>> {
if let Method::Bytecode(bc) = self.0.read().constructor { if let Method::Bytecode(bc) = self.0.read().constructor {
Some(bc.txunit) Some(bc.txunit)
@ -781,14 +773,6 @@ impl<'gc> ClassObject<'gc> {
self.0.read().superclass_object self.0.read().superclass_object
} }
pub fn set_param(self, gc_context: &Mutation<'gc>, param: Option<Option<ClassObject<'gc>>>) {
self.0.write(gc_context).params = param;
}
pub fn as_class_params(self) -> Option<Option<ClassObject<'gc>>> {
self.0.read().params
}
fn instance_allocator(self) -> Option<AllocatorFn> { fn instance_allocator(self) -> Option<AllocatorFn> {
Some(self.0.read().instance_allocator.0) Some(self.0.read().instance_allocator.0)
} }
@ -947,7 +931,7 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
Value::Null => None, Value::Null => None,
v => Some(v), v => Some(v),
}; };
let object_param = match object_param { let class_param = match object_param {
None => None, None => None,
Some(cls) => Some( Some(cls) => Some(
cls.as_object() cls.as_object()
@ -958,38 +942,12 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
"Cannot apply class {:?} with non-class parameter", "Cannot apply class {:?} with non-class parameter",
self_class.name() self_class.name()
) )
})?, })?
.inner_class_definition(),
), ),
}; };
if let Some(application) = self.0.read().applications.get(&object_param) { self.parametrize(activation, class_param)
return Ok(*application);
}
// if it's not a known application, then it's not int/uint/Number/*,
// so it must be a simple Vector.<*>-derived class.
let class_param = object_param.map(|c| c.inner_class_definition());
let parameterized_class =
Class::with_type_param(&mut activation.context, self_class, class_param);
// NOTE: this isn't fully accurate, but much simpler.
// FP's Vector is more of special case that literally copies some parent class's properties
// main example: Vector.<Object>.prototype === Vector.<*>.prototype
let vector_star_cls = activation.avm2().classes().object_vector;
let class_object =
Self::from_class(activation, parameterized_class, Some(vector_star_cls))?;
class_object.0.write(activation.context.gc_context).params = Some(object_param);
self.0
.write(activation.context.gc_context)
.applications
.insert(object_param, class_object);
Ok(class_object)
} }
} }

View File

@ -113,8 +113,8 @@ impl<'gc> ErrorObject<'gc> {
.try_read() .try_read()
.map(|obj| { .map(|obj| {
obj.base obj.base
.instance_of() .instance_class()
.map(|cls| cls.debug_class_name()) .map(|cls| cls.debug_name())
.unwrap_or_else(|| Box::new("None")) .unwrap_or_else(|| Box::new("None"))
}) })
.unwrap_or_else(|err| Box::new(err)) .unwrap_or_else(|err| Box::new(err))

View File

@ -123,7 +123,7 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> {
val @ Value::Integer(_) => Ok(val), val @ Value::Integer(_) => Ok(val),
_ => { _ => {
let class_name = self let class_name = self
.instance_of_class_definition() .instance_class()
.map(|c| c.name().local_name()) .map(|c| c.name().local_name())
.unwrap_or_else(|| "Object".into()); .unwrap_or_else(|| "Object".into());

View File

@ -1,6 +1,7 @@
//! Default AVM2 object impl //! Default AVM2 object impl
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::dynamic_map::{DynamicKey, DynamicMap}; use crate::avm2::dynamic_map::{DynamicKey, DynamicMap};
use crate::avm2::error; use crate::avm2::error;
use crate::avm2::object::{ClassObject, FunctionObject, Object, ObjectPtr, TObject}; use crate::avm2::object::{ClassObject, FunctionObject, Object, ObjectPtr, TObject};
@ -164,7 +165,7 @@ impl<'gc> ScriptObjectData<'gc> {
activation, activation,
error::ReferenceErrorCode::InvalidRead, error::ReferenceErrorCode::InvalidRead,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
@ -174,7 +175,7 @@ impl<'gc> ScriptObjectData<'gc> {
activation, activation,
error::ReferenceErrorCode::InvalidRead, error::ReferenceErrorCode::InvalidRead,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
}; };
@ -205,7 +206,7 @@ impl<'gc> ScriptObjectData<'gc> {
activation, activation,
error::ReferenceErrorCode::InvalidRead, error::ReferenceErrorCode::InvalidRead,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} else { } else {
Ok(Value::Undefined) Ok(Value::Undefined)
@ -223,7 +224,7 @@ impl<'gc> ScriptObjectData<'gc> {
activation, activation,
error::ReferenceErrorCode::InvalidWrite, error::ReferenceErrorCode::InvalidWrite,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
} }
@ -232,7 +233,7 @@ impl<'gc> ScriptObjectData<'gc> {
activation, activation,
error::ReferenceErrorCode::InvalidWrite, error::ReferenceErrorCode::InvalidWrite,
multiname, multiname,
self.instance_of(), self.instance_class(),
)); ));
}; };
@ -394,14 +395,18 @@ impl<'gc> ScriptObjectData<'gc> {
self.instance_of self.instance_of
} }
pub fn instance_class(&self) -> Option<Class<'gc>> {
self.instance_of.map(|cls| cls.inner_class_definition())
}
/// Get the vtable for this object, if it has one. /// Get the vtable for this object, if it has one.
pub fn vtable(&self) -> Option<VTable<'gc>> { pub fn vtable(&self) -> Option<VTable<'gc>> {
self.vtable self.vtable
} }
pub fn is_sealed(&self) -> bool { pub fn is_sealed(&self) -> bool {
self.instance_of() self.instance_class()
.map(|cls| cls.inner_class_definition().is_sealed()) .map(|cls| cls.is_sealed())
.unwrap_or(false) .unwrap_or(false)
} }
@ -416,9 +421,7 @@ impl<'gc> ScriptObjectData<'gc> {
} }
pub fn debug_class_name(&self) -> Box<dyn std::fmt::Debug + 'gc> { pub fn debug_class_name(&self) -> Box<dyn std::fmt::Debug + 'gc> {
let class_name = self let class_name = self.instance_class().map(|cls| cls.debug_name());
.instance_of()
.map(|class_obj| class_obj.debug_class_name());
match class_name { match class_name {
Some(class_name) => Box::new(class_name), Some(class_name) => Box::new(class_name),

View File

@ -19,7 +19,8 @@ pub fn vector_allocator<'gc>(
let base = ScriptObjectData::new(class); let base = ScriptObjectData::new(class);
let param_type = class let param_type = class
.as_class_params() .inner_class_definition()
.param()
.ok_or("Cannot convert to unparametrized Vector")?; .ok_or("Cannot convert to unparametrized Vector")?;
Ok(VectorObject(GcCell::new( Ok(VectorObject(GcCell::new(
@ -65,10 +66,10 @@ impl<'gc> VectorObject<'gc> {
vector: VectorStorage<'gc>, vector: VectorStorage<'gc>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> { ) -> Result<Object<'gc>, Error<'gc>> {
let value_type = vector.value_type().map(|o| o.into()).unwrap_or(Value::Null); let value_type = vector.value_type();
let vector_class = activation.avm2().classes().generic_vector; let vector_class = activation.avm2().classes().generic_vector;
let applied_class = vector_class.apply(activation, &[value_type])?; let applied_class = vector_class.parametrize(activation, value_type)?;
let object: Object<'gc> = VectorObject(GcCell::new( let object: Object<'gc> = VectorObject(GcCell::new(
activation.context.gc_context, activation.context.gc_context,
@ -129,12 +130,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
if name.contains_public_namespace() { if name.contains_public_namespace() {
if let Some(name) = name.local_name() { if let Some(name) = name.local_name() {
if let Ok(index) = name.parse::<usize>() { if let Ok(index) = name.parse::<usize>() {
let type_of = self let type_of = self.0.read().vector.value_type_for_coercion(activation);
.0
.read()
.vector
.value_type_for_coercion(activation)
.inner_class_definition();
let value = match value.coerce_to_type(activation, type_of)? { let value = match value.coerce_to_type(activation, type_of)? {
Value::Undefined => self.0.read().vector.default(activation), Value::Undefined => self.0.read().vector.default(activation),
Value::Null => self.0.read().vector.default(activation), Value::Null => self.0.read().vector.default(activation),
@ -165,12 +161,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
if name.contains_public_namespace() { if name.contains_public_namespace() {
if let Some(name) = name.local_name() { if let Some(name) = name.local_name() {
if let Ok(index) = name.parse::<usize>() { if let Ok(index) = name.parse::<usize>() {
let type_of = self let type_of = self.0.read().vector.value_type_for_coercion(activation);
.0
.read()
.vector
.value_type_for_coercion(activation)
.inner_class_definition();
let value = match value.coerce_to_type(activation, type_of)? { let value = match value.coerce_to_type(activation, type_of)? {
Value::Undefined => self.0.read().vector.default(activation), Value::Undefined => self.0.read().vector.default(activation),
Value::Null => self.0.read().vector.default(activation), Value::Null => self.0.read().vector.default(activation),

View File

@ -2,7 +2,7 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::Class; use crate::avm2::class::Class;
use crate::avm2::method::{BytecodeMethod, ResolvedParamConfig}; use crate::avm2::method::{BytecodeMethod, ResolvedParamConfig};
use crate::avm2::multiname::Multiname; use crate::avm2::multiname::Multiname;
use crate::avm2::object::{ClassObject, TObject}; use crate::avm2::object::TObject;
use crate::avm2::op::Op; use crate::avm2::op::Op;
use crate::avm2::property::Property; use crate::avm2::property::Property;
use crate::avm2::verify::JumpSources; use crate::avm2::verify::JumpSources;
@ -147,11 +147,6 @@ impl<'gc> Stack<'gc> {
Self(Vec::new()) Self(Vec::new())
} }
fn push_class_object(&mut self, class: ClassObject<'gc>) {
self.0
.push(OptValue::of_type(class.inner_class_definition()));
}
fn push_class(&mut self, class: Class<'gc>) { fn push_class(&mut self, class: Class<'gc>) {
self.0.push(OptValue::of_type(class)); self.0.push(OptValue::of_type(class));
} }
@ -867,7 +862,7 @@ pub fn optimize<'gc>(
stack_push_done = true; stack_push_done = true;
if let Some(class) = class { if let Some(class) = class {
stack.push_class_object(class); stack.push_class(class);
} else { } else {
stack.push_any(); stack.push_any();
} }
@ -1292,8 +1287,8 @@ pub fn optimize<'gc>(
let global_scope = outer_scope.get_unchecked(0); let global_scope = outer_scope.get_unchecked(0);
stack_push_done = true; stack_push_done = true;
if let Some(class) = global_scope.values().instance_of() { if let Some(class) = global_scope.values().instance_class() {
stack.push_class_object(class); stack.push_class(class);
} else { } else {
stack.push_any(); stack.push_any();
} }
@ -1310,7 +1305,7 @@ pub fn optimize<'gc>(
if !outer_scope.is_empty() { if !outer_scope.is_empty() {
let global_scope = outer_scope.get_unchecked(0); let global_scope = outer_scope.get_unchecked(0);
if let Some(class) = global_scope.values().instance_of() { if let Some(class) = global_scope.values().instance_class() {
let mut value_class = let mut value_class =
class.instance_vtable().slot_classes()[*slot_id as usize]; class.instance_vtable().slot_classes()[*slot_id as usize];
let resolved_value_class = value_class.get_class(activation); let resolved_value_class = value_class.get_class(activation);

View File

@ -1,8 +1,9 @@
//! Represents AVM2 scope chain resolution. //! Represents AVM2 scope chain resolution.
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::domain::Domain; use crate::avm2::domain::Domain;
use crate::avm2::object::{ClassObject, Object, TObject}; use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::avm2::{Multiname, Namespace}; use crate::avm2::{Multiname, Namespace};
@ -237,7 +238,7 @@ impl<'gc> ScopeChain<'gc> {
pub fn get_entry_for_multiname( pub fn get_entry_for_multiname(
&self, &self,
multiname: &Multiname<'gc>, multiname: &Multiname<'gc>,
) -> Option<Option<(Option<ClassObject<'gc>>, u32)>> { ) -> Option<Option<(Option<Class<'gc>>, u32)>> {
if let Some(container) = self.container { if let Some(container) = self.container {
for (index, scope) in container.scopes.iter().enumerate().skip(1).rev() { for (index, scope) in container.scopes.iter().enumerate().skip(1).rev() {
if scope.with() { if scope.with() {
@ -248,7 +249,7 @@ impl<'gc> ScopeChain<'gc> {
let values = scope.values(); let values = scope.values();
if values.has_trait(multiname) { if values.has_trait(multiname) {
return Some(Some((values.instance_of(), index as u32))); return Some(Some((values.instance_class(), index as u32)));
} }
} }
} }

View File

@ -620,11 +620,7 @@ impl<'gc> Script<'gc> {
globals.vtable().unwrap().init_vtable( globals.vtable().unwrap().init_vtable(
globals.instance_of(), globals.instance_of(),
globals globals.instance_class().unwrap().protected_namespace(),
.instance_of()
.unwrap()
.inner_class_definition()
.protected_namespace(),
&self.traits()?, &self.traits()?,
Some(scope), Some(scope),
None, None,

View File

@ -1,8 +1,8 @@
//! Storage for AS3 Vectors //! Storage for AS3 Vectors
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::error::range_error; use crate::avm2::error::range_error;
use crate::avm2::object::{ClassObject, Object};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::Collect; use gc_arena::Collect;
@ -29,21 +29,21 @@ pub struct VectorStorage<'gc> {
is_fixed: bool, is_fixed: bool,
/// The allowed type of the contents of the vector, in the form of a class /// The allowed type of the contents of the vector, in the form of a class
/// object. None represents a Vector.<*>. /// None represents a Vector.<*>.
/// ///
/// Vector typing is enforced by one of two ways: either by generating /// Vector typing is enforced by one of two ways: either by generating
/// exceptions on values that are not of the given type, or by coercing /// exceptions on values that are not of the given type, or by coercing
/// incorrectly typed values to the given type if possible. Values that do /// incorrectly typed values to the given type if possible. Values that do
/// not coerce are replaced with the default value for the given value /// not coerce are replaced with the default value for the given value
/// type. /// type.
value_type: Option<ClassObject<'gc>>, value_type: Option<Class<'gc>>,
} }
impl<'gc> VectorStorage<'gc> { impl<'gc> VectorStorage<'gc> {
pub fn new( pub fn new(
length: usize, length: usize,
is_fixed: bool, is_fixed: bool,
value_type: Option<ClassObject<'gc>>, value_type: Option<Class<'gc>>,
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Self { ) -> Self {
let storage = Vec::new(); let storage = Vec::new();
@ -79,7 +79,7 @@ impl<'gc> VectorStorage<'gc> {
pub fn from_values( pub fn from_values(
storage: Vec<Value<'gc>>, storage: Vec<Value<'gc>>,
is_fixed: bool, is_fixed: bool,
value_type: Option<ClassObject<'gc>>, value_type: Option<Class<'gc>>,
) -> Self { ) -> Self {
VectorStorage { VectorStorage {
storage, storage,
@ -118,11 +118,11 @@ impl<'gc> VectorStorage<'gc> {
/// Get the default value for this vector. /// Get the default value for this vector.
pub fn default(&self, activation: &mut Activation<'_, 'gc>) -> Value<'gc> { pub fn default(&self, activation: &mut Activation<'_, 'gc>) -> Value<'gc> {
if let Some(value_type) = self.value_type { if let Some(value_type) = self.value_type {
if Object::ptr_eq(value_type, activation.avm2().classes().int) if value_type == activation.avm2().classes().int.inner_class_definition()
|| Object::ptr_eq(value_type, activation.avm2().classes().uint) || value_type == activation.avm2().classes().uint.inner_class_definition()
{ {
Value::Integer(0) Value::Integer(0)
} else if Object::ptr_eq(value_type, activation.avm2().classes().number) { } else if value_type == activation.avm2().classes().number.inner_class_definition() {
Value::Number(0.0) Value::Number(0.0)
} else { } else {
Value::Null Value::Null
@ -133,17 +133,14 @@ impl<'gc> VectorStorage<'gc> {
} }
/// Get the value type stored in this vector (same as the class <T> type). /// Get the value type stored in this vector (same as the class <T> type).
pub fn value_type(&self) -> Option<ClassObject<'gc>> { pub fn value_type(&self) -> Option<Class<'gc>> {
self.value_type self.value_type
} }
/// Get the value type this vector coerces things to. /// Get the value type this vector coerces things to.
pub fn value_type_for_coercion( pub fn value_type_for_coercion(&self, activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
&self,
activation: &mut Activation<'_, 'gc>,
) -> ClassObject<'gc> {
self.value_type self.value_type
.unwrap_or_else(|| activation.avm2().classes().object) .unwrap_or_else(|| activation.avm2().classes().object.inner_class_definition())
} }
/// Check if a vector index is in bounds. /// Check if a vector index is in bounds.
@ -251,11 +248,11 @@ impl<'gc> VectorStorage<'gc> {
if let Some(v) = self.storage.pop() { if let Some(v) = self.storage.pop() {
Ok(v) Ok(v)
} else if let Some(value_type) = self.value_type() { } else if let Some(value_type) = self.value_type() {
if Object::ptr_eq(value_type, activation.avm2().classes().uint) if value_type == activation.avm2().classes().uint.inner_class_definition()
|| Object::ptr_eq(value_type, activation.avm2().classes().int) || value_type == activation.avm2().classes().int.inner_class_definition()
{ {
Ok(Value::Integer(0)) Ok(Value::Integer(0))
} else if Object::ptr_eq(value_type, activation.avm2().classes().number) { } else if value_type == activation.avm2().classes().number.inner_class_definition() {
Ok(Value::Number(0.0)) Ok(Value::Number(0.0))
} else { } else {
Ok(Value::Undefined) Ok(Value::Undefined)
@ -297,11 +294,11 @@ impl<'gc> VectorStorage<'gc> {
if !self.storage.is_empty() { if !self.storage.is_empty() {
Ok(self.storage.remove(0)) Ok(self.storage.remove(0))
} else if let Some(value_type) = self.value_type() { } else if let Some(value_type) = self.value_type() {
if Object::ptr_eq(value_type, activation.avm2().classes().uint) if value_type == activation.avm2().classes().uint.inner_class_definition()
|| Object::ptr_eq(value_type, activation.avm2().classes().int) || value_type == activation.avm2().classes().int.inner_class_definition()
{ {
Ok(Value::Integer(0)) Ok(Value::Integer(0))
} else if Object::ptr_eq(value_type, activation.avm2().classes().number) { } else if value_type == activation.avm2().classes().number.inner_class_definition() {
Ok(Value::Number(0.0)) Ok(Value::Number(0.0))
} else { } else {
Ok(Value::Undefined) Ok(Value::Undefined)

View File

@ -438,7 +438,7 @@ fn object_name<'gc>(mc: &Mutation<'gc>, object: Object<'gc>) -> String {
.to_string() .to_string()
} else { } else {
let name = object let name = object
.instance_of_class_definition() .instance_class()
.map(|r| Cow::Owned(r.name().local_name().to_string())) .map(|r| Cow::Owned(r.name().local_name().to_string()))
.unwrap_or(Cow::Borrowed("Object")); .unwrap_or(Cow::Borrowed("Object"));
format!("{} {:p}", name, object.as_ptr()) format!("{} {:p}", name, object.as_ptr())

View File

@ -64,9 +64,12 @@ impl<'gc> BitmapClass<'gc> {
class: Avm2ClassObject<'gc>, class: Avm2ClassObject<'gc>,
context: &mut UpdateContext<'_, 'gc>, context: &mut UpdateContext<'_, 'gc>,
) -> Option<Self> { ) -> Option<Self> {
if class.has_class_in_chain(context.avm2.classes().bitmap.inner_class_definition()) { let class_definition = class.inner_class_definition();
if class_definition
.has_class_in_chain(context.avm2.classes().bitmap.inner_class_definition())
{
Some(BitmapClass::Bitmap(class)) Some(BitmapClass::Bitmap(class))
} else if class } else if class_definition
.has_class_in_chain(context.avm2.classes().bitmapdata.inner_class_definition()) .has_class_in_chain(context.avm2.classes().bitmapdata.inner_class_definition())
{ {
Some(BitmapClass::BitmapData(class)) Some(BitmapClass::BitmapData(class))