From f504e9d0b45b5ffaee4d37b5803898cd0b12fe52 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 17 Jul 2023 22:42:34 +0200 Subject: [PATCH] avm2: Refactor Vector. class creation --- core/src/avm2/class.rs | 88 ++++++------- core/src/avm2/domain.rs | 8 +- core/src/avm2/globals.rs | 74 ++++++++++- core/src/avm2/globals/vector.rs | 172 +++++++++++--------------- core/src/avm2/object/class_object.rs | 107 +++++----------- core/src/avm2/object/vector_object.rs | 2 +- core/src/avm2/value.rs | 19 --- 7 files changed, 216 insertions(+), 254 deletions(-) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 9a01d5640..a84604998 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -78,7 +78,7 @@ pub struct Class<'gc> { name: QName<'gc>, /// The type parameter for this class (only supported for Vector) - param: Option>>, + param: Option>>>, /// The name of this class's superclass. super_class: Option>, @@ -139,12 +139,6 @@ pub struct Class<'gc> { /// If None, a simple coercion is done. call_handler: Option>, - /// The class initializer for specializations of this class. - /// - /// Only applies for generic classes. Must be called once and only once - /// per specialization, prior to any use of the specialized class. - specialized_class_init: Method<'gc>, - /// Static traits for a given class. /// /// These are accessed as class object properties. @@ -156,7 +150,7 @@ pub struct Class<'gc> { /// Maps a type parameter to the application of this class with that parameter. /// /// Only applicable if this class is generic. - applications: FnvHashMap, GcCell<'gc, Class<'gc>>>, + applications: FnvHashMap>, GcCell<'gc, Class<'gc>>>, /// Whether or not this is a system-defined class. /// @@ -220,11 +214,6 @@ impl<'gc> Class<'gc> { class_initializer_called: false, call_handler: None, class_traits: Vec::new(), - specialized_class_init: Method::from_builtin( - |_, _, _| Ok(Value::Undefined), - "", - mc, - ), traits_loaded: true, is_system: true, applications: FnvHashMap::default(), @@ -232,45 +221,55 @@ impl<'gc> Class<'gc> { ) } + pub fn add_application( + &mut self, + param: Option>>, + cls: GcCell<'gc, Class<'gc>>, + ) { + let key = param.map(ClassKey); + self.applications.insert(key, cls); + } + /// Apply type parameters to an existing class. /// /// This is used to parameterize a generic type. The returned class will no /// longer be generic. pub fn with_type_param( this: GcCell<'gc, Class<'gc>>, - param: GcCell<'gc, Class<'gc>>, + param: Option>>, mc: MutationContext<'gc, '_>, ) -> GcCell<'gc, Class<'gc>> { let read = this.read(); - let key = ClassKey(param); + let key = param.map(ClassKey); if let Some(application) = read.applications.get(&key) { return *application; } - let mut new_class = (*read).clone(); + // This can only happen for non-builtin Vector types, + // so let's create one here directly. - new_class.param = Some(param); - new_class.attributes.remove(ClassAttributes::GENERIC); - new_class.class_init = new_class.specialized_class_init.clone(); - new_class.class_initializer_called = false; + let object_vector_cls = read + .applications + .get(&None) + .expect("Vector.<*> not initialized?"); - // FIXME - we should store a `Multiname` instead of a `QName`, and use the - // `params` field. For now, this is good enough to get tests passing - let name_with_params = format!( - "{}.<{}>", - new_class.name.local_name(), - param.read().name().to_qualified_name(mc) + let param = param.expect("Trying to create Vector<*>, which shouldn't happen here"); + let name = format!("Vector.<{}>", param.read().name().to_qualified_name(mc)); + + let new_class = Self::new( + // FIXME - we should store a `Multiname` instead of a `QName`, and use the + // `params` field. For now, this is good enough to get tests passing + QName::new(read.name.namespace(), AvmString::new_utf8(mc, name)), + Some(Multiname::new(read.name.namespace(), "Vector.<*>")), + object_vector_cls.read().instance_init(), + object_vector_cls.read().class_init(), + mc, ); + new_class.write(mc).param = Some(Some(param)); + new_class.write(mc).call_handler = object_vector_cls.read().call_handler(); - new_class.name = QName::new( - new_class.name.namespace(), - AvmString::new_utf8(mc, name_with_params), - ); - - let new_class = GcCell::new(mc, new_class); drop(read); - this.write(mc).applications.insert(key, new_class); new_class } @@ -395,11 +394,6 @@ impl<'gc> Class<'gc> { class_initializer_called: false, call_handler: native_call_handler, class_traits: Vec::new(), - specialized_class_init: Method::from_builtin( - |_, _, _| Ok(Value::Undefined), - "", - activation.context.gc_context, - ), traits_loaded: false, is_system: false, applications: Default::default(), @@ -564,11 +558,6 @@ impl<'gc> Class<'gc> { "", activation.context.gc_context, ), - specialized_class_init: Method::from_builtin( - |_, _, _| Ok(Value::Undefined), - "", - activation.context.gc_context, - ), class_initializer_called: false, call_handler: None, class_traits: Vec::new(), @@ -587,6 +576,10 @@ impl<'gc> Class<'gc> { self.name = name; } + pub fn set_param(&mut self, param: Option>>>) { + self.param = param; + } + pub fn super_class_name(&self) -> &Option> { &self.super_class } @@ -815,11 +808,6 @@ impl<'gc> Class<'gc> { self.class_initializer_called = true; } - /// Set the class initializer for specializations of this class. - pub fn set_specialized_init(&mut self, specialized_init: Method<'gc>) { - self.specialized_class_init = specialized_init; - } - pub fn direct_interfaces(&self) -> &[Multiname<'gc>] { &self.direct_interfaces } @@ -847,10 +835,6 @@ impl<'gc> Class<'gc> { pub fn is_generic(&self) -> bool { self.attributes.contains(ClassAttributes::GENERIC) } - - pub fn param(&self) -> &Option>> { - &self.param - } } pub struct ClassHashWrapper<'gc>(pub GcCell<'gc, Class<'gc>>); diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 754de544e..2ad344caa 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -183,9 +183,15 @@ impl<'gc> Domain<'gc> { if let Some(param) = multiname.param() { if !param.is_any_name() { if let Some(resolved_param) = self.get_class(¶m, mc)? { - return Ok(Some(Class::with_type_param(class, resolved_param, mc))); + return Ok(Some(Class::with_type_param( + class, + Some(resolved_param), + mc, + ))); } return Ok(None); + } else { + return Ok(Some(Class::with_type_param(class, None, mc))); } } } diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 0fa2568b8..40c86d224 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -83,7 +83,8 @@ pub struct SystemClasses<'gc> { pub sprite: ClassObject<'gc>, pub simplebutton: ClassObject<'gc>, pub regexp: ClassObject<'gc>, - pub vector: ClassObject<'gc>, + pub generic_vector: ClassObject<'gc>, + pub object_vector: ClassObject<'gc>, // Vector.<*> pub soundtransform: ClassObject<'gc>, pub soundchannel: ClassObject<'gc>, pub bitmap: ClassObject<'gc>, @@ -203,7 +204,8 @@ impl<'gc> SystemClasses<'gc> { sprite: object, simplebutton: object, regexp: object, - vector: object, + generic_vector: object, + object_vector: object, soundtransform: object, soundchannel: object, bitmap: object, @@ -364,6 +366,41 @@ fn class<'gc>( Ok(class_object) } +fn vector_class<'gc>( + param_class: Option>, + legacy_name: &'static str, + script: Script<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let mc = activation.context.gc_context; + let (_, mut global, mut domain) = script.init(); + + let cls = param_class.map(|c| c.inner_class_definition()); + let vector_cls = class( + vector::create_builtin_class(activation, cls), + script, + activation, + )?; + vector_cls.set_param(activation, Some(param_class)); + + let generic_vector = activation.avm2().classes().generic_vector; + generic_vector.add_application(activation, param_class, vector_cls); + let generic_cls = generic_vector.inner_class_definition(); + generic_cls + .write(mc) + .add_application(cls, vector_cls.inner_class_definition()); + + let legacy_name = QName::new(activation.avm2().vector_internal_namespace, legacy_name); + global.install_const_late( + mc, + legacy_name, + vector_cls.into(), + activation.avm2().classes().class, + ); + domain.export_definition(legacy_name, script, mc); + Ok(vector_cls) +} + macro_rules! avm2_system_class { ($field:ident, $activation:ident, $class:expr, $script:expr) => { let class_object = class($class, $script, $activation)?; @@ -569,7 +606,38 @@ pub fn load_player_globals<'gc>( )?; function(activation, "", "unescape", toplevel::unescape, script)?; - avm2_system_class!(vector, activation, vector::create_class(activation), script); + avm2_system_class!( + generic_vector, + activation, + vector::create_generic_class(activation), + script + ); + + vector_class( + Some(activation.avm2().classes().int), + "Vector$int", + script, + activation, + )?; + vector_class( + Some(activation.avm2().classes().uint), + "Vector$uint", + script, + activation, + )?; + vector_class( + Some(activation.avm2().classes().number), + "Vector$double", + script, + activation, + )?; + let object_vector = vector_class(None, "Vector$object", script, activation)?; + activation + .avm2() + .system_classes + .as_mut() + .unwrap() + .object_vector = object_vector; avm2_system_class!(date, activation, date::create_class(activation), script); diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 1ce383e9b..dbb2f0d94 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -2,12 +2,15 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::error::type_error; use crate::avm2::globals::array::{ compare_numeric, compare_string_case_insensitive, compare_string_case_sensitive, ArrayIter, SortOptions, }; use crate::avm2::method::{Method, NativeMethodImpl}; -use crate::avm2::object::{vector_allocator, FunctionObject, Object, TObject, VectorObject}; +use crate::avm2::object::{ + vector_allocator, ClassObject, FunctionObject, Object, TObject, VectorObject, +}; use crate::avm2::value::Value; use crate::avm2::vector::VectorStorage; use crate::avm2::Error; @@ -17,6 +20,17 @@ use crate::string::AvmString; use gc_arena::GcCell; use std::cmp::{max, min, Ordering}; +pub fn generic_vector_allocator<'gc>( + _class: ClassObject<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + return Err(Error::AvmError(type_error( + activation, + "Error #1007: Instantiation attempted on a non-constructor.", + 1007, + )?)); +} + /// Implements `Vector`'s instance constructor. pub fn instance_init<'gc>( activation: &mut Activation<'_, 'gc>, @@ -56,7 +70,7 @@ fn class_call<'gc>( let this_class = activation.subclass_object().unwrap(); let value_type = this_class .as_class_params() - .ok_or("Cannot convert to Vector")? // note: ideally, an untyped Vector shouldn't have a call handler at all + .ok_or("Cannot convert to Vector")? // technically unreachable .unwrap_or(activation.avm2().classes().object); let arg = args.get(0).cloned().unwrap(); @@ -84,96 +98,15 @@ fn class_call<'gc>( Ok(VectorObject::from_vector(new_storage, activation)?.into()) } -/// Implements `Vector`'s class constructor. -pub fn class_init<'gc>( +pub fn generic_init<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let mut globals = activation.global_scope().unwrap(); - let mut domain = activation.domain(); - - let vector_internal_namespace = activation.avm2().vector_internal_namespace; - - //We have to grab Object's defining script instead of our own, because - //at this point Vector hasn't actually been defined yet. It doesn't - //matter because we only have one script for our globals. - let (_, script) = domain - .get_defining_script(&Multiname::new( - activation.avm2().public_namespace, - "Object", - ))? - .unwrap(); - - let class_class = activation.avm2().classes().class; - let int_class = activation.avm2().classes().int; - let int_vector_class = this.apply(activation, int_class.into())?; - let int_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$int"); - - globals.install_const_late( - activation.context.gc_context, - int_vector_name_legacy, - int_vector_class.into(), - class_class, - ); - domain.export_definition( - int_vector_name_legacy, - script, - activation.context.gc_context, - ); - - let uint_class = activation.avm2().classes().uint; - let uint_vector_class = this.apply(activation, uint_class.into())?; - let uint_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$uint"); - - globals.install_const_late( - activation.context.gc_context, - uint_vector_name_legacy, - uint_vector_class.into(), - class_class, - ); - domain.export_definition( - uint_vector_name_legacy, - script, - activation.context.gc_context, - ); - - let number_class = activation.avm2().classes().number; - let number_vector_class = this.apply(activation, number_class.into())?; - let number_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$double"); - - globals.install_const_late( - activation.context.gc_context, - number_vector_name_legacy, - number_vector_class.into(), - class_class, - ); - domain.export_definition( - number_vector_name_legacy, - script, - activation.context.gc_context, - ); - - let plain_vector_class = this.apply(activation, Value::Null)?; - let object_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$object"); - - globals.install_const_late( - activation.context.gc_context, - object_vector_name_legacy, - plain_vector_class.into(), - class_class, - ); - domain.export_definition( - object_vector_name_legacy, - script, - activation.context.gc_context, - ); - - Ok(Value::Undefined) + activation.super_init(this, args) } -/// Implements `Vector`'s specialized-class constructor. -pub fn specialized_class_init<'gc>( +fn class_init<'gc>( activation: &mut Activation<'_, 'gc>, this: Object<'gc>, _args: &[Value<'gc>], @@ -307,9 +240,6 @@ pub fn concat<'gc>( return Err("Not a vector-structured object".into()); }; - let my_class = this - .instance_of() - .ok_or("TypeError: Tried to concat into a bare object")?; let val_class = new_vector_storage.value_type().inner_class_definition(); for arg in args { @@ -319,11 +249,16 @@ pub fn concat<'gc>( let arg_class = arg_obj .instance_of_class_definition() .ok_or("TypeError: Tried to concat from a bare object")?; - if !arg.is_of_type(activation, my_class.inner_class_definition()) { + + // this is Vector. + let my_base_vector_class = activation + .subclass_object() + .expect("Method call without bound class?"); + if !arg.is_of_type(activation, my_base_vector_class.inner_class_definition()) { return Err(format!( "TypeError: Cannot coerce argument of type {:?} to argument of type {:?}", arg_class.read().name(), - my_class.inner_class_definition().read().name() + my_base_vector_class.inner_class_definition().read().name() ) .into()); } @@ -945,28 +880,61 @@ pub fn splice<'gc>( } /// Construct `Vector`'s class. -pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { +pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().vector_public_namespace, "Vector"), Some(Multiname::new(activation.avm2().public_namespace, "Object")), - Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), + Method::from_builtin(generic_init, "", mc), + Method::from_builtin(generic_init, "", mc), + mc, + ); + + let mut write = class.write(mc); + write.set_attributes(ClassAttributes::GENERIC | ClassAttributes::FINAL); + write.set_instance_allocator(generic_vector_allocator); + class +} + +/// Construct `Vector.`'s class. +pub fn create_builtin_class<'gc>( + activation: &mut Activation<'_, 'gc>, + param: Option>>, +) -> GcCell<'gc, Class<'gc>> { + let mc = activation.context.gc_context; + + // FIXME - we should store a `Multiname` instead of a `QName`, and use the + // `params` field. For now, this is good enough to get tests passing + let name = if let Some(param) = param { + let name = format!("Vector.<{}>", param.read().name().to_qualified_name(mc)); + QName::new( + activation.avm2().vector_public_namespace, + AvmString::new_utf8(mc, name), + ) + } else { + QName::new(activation.avm2().vector_public_namespace, "Vector.<*>") + }; + + let class = Class::new( + name, + Some(Multiname::new(activation.avm2().public_namespace, "Object")), + Method::from_builtin(instance_init, " instance initializer>", mc), + Method::from_builtin(class_init, " class initializer>", mc), mc, ); let mut write = class.write(mc); - write.set_attributes(ClassAttributes::GENERIC | ClassAttributes::FINAL); + // TODO: Vector.<*> is also supposed to be final, but currently + // that'd make it impossible for us to create derived Vector.. + if param.is_some() { + write.set_attributes(ClassAttributes::FINAL); + } + write.set_param(Some(param)); write.set_instance_allocator(vector_allocator); - write.set_specialized_init(Method::from_builtin( - specialized_class_init, - "", - mc, - )); write.set_call_handler(Method::from_builtin( class_call, - "", + " call handler>", mc, )); diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 3a14a8a8b..97815301c 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -465,15 +465,6 @@ impl<'gc> ClassObject<'gc> { return true; } - let test_class_read = test_class.read(); - if let (Some(Some(my_param)), Some(other_single_param)) = - (class.as_class_params(), test_class_read.param()) - { - if my_param.has_class_in_chain(*other_single_param) { - return true; - } - } - my_class = class.superclass_object() } @@ -727,6 +718,18 @@ impl<'gc> ClassObject<'gc> { } } + pub fn add_application( + &self, + activation: &mut Activation<'_, 'gc>, + param: Option>, + cls: ClassObject<'gc>, + ) { + self.0 + .write(activation.context.gc_context) + .applications + .insert(param, cls); + } + pub fn translation_unit(self) -> Option> { if let Method::Bytecode(bc) = self.0.read().constructor { Some(bc.txunit) @@ -780,6 +783,14 @@ impl<'gc> ClassObject<'gc> { self.0.read().superclass_object } + pub fn set_param( + self, + activation: &mut Activation<'_, 'gc>, + param: Option>>, + ) { + self.0.write(activation.context.gc_context).params = param; + } + pub fn as_class_params(self) -> Option>> { self.0.read().params } @@ -908,10 +919,6 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { return Err(format!("Class {:?} is not generic", self_class.read().name()).into()); } - if !self_class.read().param().is_none() { - return Err(format!("Class {:?} was already applied", self_class.read().name()).into()); - } - //Because `null` is a valid parameter, we have to accept values as //parameters instead of objects. We coerce them to objects now. let object_param = match nullable_param { @@ -933,75 +940,23 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { return Ok(*application); } - let class_param = object_param - .unwrap_or(activation.avm2().classes().object) - .inner_class_definition(); + // 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: GcCell<'_, Class<'_>> = Class::with_type_param(self_class, class_param, activation.context.gc_context); - let class_scope = self.0.read().class_scope; - let instance_scope = self.0.read().instance_scope; - let instance_allocator = self.0.read().instance_allocator.clone(); - let superclass_object = self.0.read().superclass_object; + // 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..prototype === Vector.<*>.prototype - let class_proto = self.allocate_prototype(activation, superclass_object)?; + let vector_star_cls = activation.avm2().classes().object_vector; + let class_object = + Self::from_class(activation, parameterized_class, Some(vector_star_cls))?; - let class_class = activation.avm2().classes().class; - - let constructor = self.0.read().constructor.clone(); - let native_constructor = self.0.read().native_constructor.clone(); - let call_handler = self.0.read().call_handler.clone(); - - let mut class_object = ClassObject(GcCell::new( - activation.context.gc_context, - ClassObjectData { - base: ScriptObjectData::new(class_class), - class: parameterized_class, - prototype: None, - class_scope, - instance_scope, - superclass_object, - instance_allocator, - constructor, - native_constructor, - call_handler, - params: Some(object_param), - applications: Default::default(), - interfaces: Vec::new(), - instance_vtable: VTable::empty(activation.context.gc_context), - class_vtable: VTable::empty(activation.context.gc_context), - }, - )); - - class_object - .inner_class_definition() - .read() - .validate_class(class_object.superclass_object())?; - - class_object.instance_vtable().init_vtable( - class_object, - parameterized_class.read().instance_traits(), - class_object.instance_scope(), - class_object - .superclass_object() - .map(|cls| cls.instance_vtable()), - activation, - )?; - - // class vtable == class traits + Class instance traits - class_object.class_vtable().init_vtable( - class_object, - parameterized_class.read().class_traits(), - class_object.class_scope(), - Some(class_object.instance_of().unwrap().instance_vtable()), - activation, - )?; - - class_object.link_prototype(activation, class_proto)?; - class_object.link_interfaces(activation)?; - class_object.install_class_vtable_and_slots(activation.context.gc_context); - class_object.run_class_initializer(activation)?; + class_object.0.write(activation.context.gc_context).params = Some(object_param); self.0 .write(activation.context.gc_context) diff --git a/core/src/avm2/object/vector_object.rs b/core/src/avm2/object/vector_object.rs index 7c856e283..6698d3142 100644 --- a/core/src/avm2/object/vector_object.rs +++ b/core/src/avm2/object/vector_object.rs @@ -71,7 +71,7 @@ impl<'gc> VectorObject<'gc> { activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { let value_type = vector.value_type(); - let vector_class = activation.avm2().classes().vector; + let vector_class = activation.avm2().classes().generic_vector; let applied_class = vector_class.apply(activation, value_type.into())?; diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 6f46cc01e..e5895e00f 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -8,7 +8,6 @@ use crate::avm2::script::TranslationUnit; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; -use crate::avm2::QName; use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use crate::string::{AvmAtom, AvmString, WStr}; use gc_arena::{Collect, GcCell, MutationContext}; @@ -1029,24 +1028,6 @@ impl<'gc> Value<'gc> { if object.is_of_type(class, &mut activation.context) { return Ok(*self); } - - if let Some(vector) = object.as_vector_storage() { - let name = class.read().name(); - let vector_public_namespace = activation.avm2().vector_public_namespace; - let vector_internal_namespace = activation.avm2().vector_internal_namespace; - if name == QName::new(vector_public_namespace, "Vector") - || (name == QName::new(vector_internal_namespace, "Vector$int") - && vector.value_type() == activation.avm2().classes().int) - || (name == QName::new(vector_internal_namespace, "Vector$uint") - && vector.value_type() == activation.avm2().classes().uint) - || (name == QName::new(vector_internal_namespace, "Vector$number") - && vector.value_type() == activation.avm2().classes().number) - || (name == QName::new(vector_internal_namespace, "Vector$object") - && vector.value_type() == activation.avm2().classes().object) - { - return Ok(*self); - } - } } let name = class