diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 38962cb12..511e32498 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -433,7 +433,7 @@ impl<'gc> Avm2<'gc> { if let Some(object) = object.and_then(|obj| obj.upgrade(context.gc_context)) { let mut activation = Activation::from_nothing(context.reborrow()); - if object.is_of_type(on_type, &mut activation.context) { + if object.is_of_type(on_type.inner_class_definition(), &mut activation.context) { if let Err(err) = events::dispatch_event(&mut activation, object, event) { tracing::error!( "Encountered AVM2 error when broadcasting `{}` event: {:?}", diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index b0541f1ce..e22d342e7 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -909,7 +909,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { matches = true; } else if let Ok(err_object) = err_object { let type_name = self.pool_multiname_static(method, e.type_name)?; - let ty_class = self.resolve_class(&type_name)?; + let ty_class = self.resolve_class(&type_name)?.inner_class_definition(); matches = err_object.is_of_type(ty_class, &mut self.context); } @@ -2105,8 +2105,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { } fn op_check_filter(&mut self) -> Result, Error<'gc>> { - let xml = self.avm2().classes().xml; - let xml_list = self.avm2().classes().xml_list; + let xml = self.avm2().classes().xml.inner_class_definition(); + let xml_list = self.avm2().classes().xml_list.inner_class_definition(); let value = self.pop_stack().coerce_to_object_or_typeerror(self, None)?; if value.is_of_type(xml, &mut self.context) || value.is_of_type(xml_list, &mut self.context) @@ -2791,7 +2791,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); let multiname = self.pool_multiname_static(method, type_name_index)?; - let type_object = self.resolve_class(&multiname)?; + let type_object = self.resolve_class(&multiname)?.inner_class_definition(); let is_instance_of = value.is_of_type(self, type_object); self.push_stack(is_instance_of); @@ -2804,7 +2804,8 @@ impl<'a, 'gc> Activation<'a, 'gc> { .pop_stack() .as_object() .and_then(|o| o.as_class_object()) - .ok_or("Cannot check if value is of a type that is null, undefined, or not a class")?; + .ok_or("Cannot check if value is of a type that is null, undefined, or not a class")? + .inner_class_definition(); let value = self.pop_stack(); let is_instance_of = value.is_of_type(self, type_object); @@ -2821,7 +2822,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { let value = self.pop_stack(); let multiname = self.pool_multiname_static(method, type_name_index)?; - let class = self.resolve_class(&multiname)?; + let class = self.resolve_class(&multiname)?.inner_class_definition(); if value.is_of_type(self, class) { self.push_stack(value); @@ -2847,7 +2848,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { )?))?; let value = self.pop_stack(); - if value.is_of_type(self, class) { + if value.is_of_type(self, class.inner_class_definition()) { self.push_stack(value); } else { self.push_stack(Value::Null); diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 93321729d..05f7a97be 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -11,6 +11,7 @@ use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::QName; use bitflags::bitflags; +use fnv::FnvHashMap; use gc_arena::{Collect, GcCell, MutationContext}; use std::fmt; use std::hash::{Hash, Hasher}; @@ -152,6 +153,11 @@ pub struct Class<'gc> { /// Whether or not this `Class` has loaded its traits or not. traits_loaded: bool, + /// 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>>>, + /// Whether or not this is a system-defined class. /// /// System defined classes are allowed to have illegal trait configurations @@ -159,6 +165,26 @@ pub struct Class<'gc> { is_system: bool, } +/// Allows using a `GcCell<'gc, Class<'gc>>` as a HashMap key, +/// using the pointer address for hashing/equality. +#[derive(Collect, Copy, Clone)] +#[collect(no_drop)] +struct ClassKey<'gc>(GcCell<'gc, Class<'gc>>); + +impl PartialEq for ClassKey<'_> { + fn eq(&self, other: &Self) -> bool { + GcCell::ptr_eq(self.0, other.0) + } +} + +impl Eq for ClassKey<'_> {} + +impl Hash for ClassKey<'_> { + fn hash(&self, state: &mut H) { + self.0.as_ptr().hash(state); + } +} + impl<'gc> Class<'gc> { /// Create a new class. /// @@ -201,6 +227,7 @@ impl<'gc> Class<'gc> { ), traits_loaded: true, is_system: true, + applications: FnvHashMap::default(), }, ) } @@ -210,11 +237,18 @@ impl<'gc> Class<'gc> { /// This is used to parameterize a generic type. The returned class will no /// longer be generic. pub fn with_type_param( - &self, + this: GcCell<'gc, Class<'gc>>, param: GcCell<'gc, Class<'gc>>, mc: MutationContext<'gc, '_>, ) -> GcCell<'gc, Class<'gc>> { - let mut new_class = self.clone(); + let read = this.read(); + let key = ClassKey(param); + + if let Some(application) = read.applications.get(&key) { + return *application; + } + + let mut new_class = (*read).clone(); new_class.param = Some(param); new_class.attributes.remove(ClassAttributes::GENERIC); @@ -234,7 +268,11 @@ impl<'gc> Class<'gc> { AvmString::new_utf8(mc, name_with_params), ); - GcCell::allocate(mc, new_class) + let new_class = GcCell::allocate(mc, new_class); + drop(read); + + this.write(mc).applications.insert(key, new_class); + new_class } /// Set the attributes of the class (sealed/final/interface status). @@ -364,6 +402,7 @@ impl<'gc> Class<'gc> { ), traits_loaded: false, is_system: false, + applications: Default::default(), }, )) } @@ -473,7 +512,7 @@ impl<'gc> Class<'gc> { } if instance_trait.is_override() && !did_override { - return Err(format!("VerifyError: Trait {} in class {} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name().local_name()).into()); + return Err(format!("VerifyError: Trait {} in class {:?} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name()).into()); } } } @@ -535,6 +574,7 @@ impl<'gc> Class<'gc> { class_traits: Vec::new(), traits_loaded: true, is_system: false, + applications: Default::default(), }, )) } diff --git a/core/src/avm2/domain.rs b/core/src/avm2/domain.rs index 640fefd79..a2d25f230 100644 --- a/core/src/avm2/domain.rs +++ b/core/src/avm2/domain.rs @@ -149,7 +149,7 @@ impl<'gc> Domain<'gc> { Ok(None) } - pub fn get_class( + fn get_class_inner( self, multiname: &Multiname<'gc>, ) -> Result>>, Error<'gc>> { @@ -159,12 +159,32 @@ impl<'gc> Domain<'gc> { } if let Some(parent) = read.parent { - return parent.get_class(multiname); + return parent.get_class_inner(multiname); } Ok(None) } + pub fn get_class( + self, + multiname: &Multiname<'gc>, + mc: MutationContext<'gc, '_>, + ) -> Result>>, Error<'gc>> { + let class = self.get_class_inner(multiname)?; + + if let Some(class) = class { + 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(None); + } + } + } + Ok(class) + } + /// Resolve a Multiname and return the script that provided it. /// /// If a name does not exist or cannot be resolved, an error will be thrown. diff --git a/core/src/avm2/filters.rs b/core/src/avm2/filters.rs index d166bdf5b..6f9f19a8b 100644 --- a/core/src/avm2/filters.rs +++ b/core/src/avm2/filters.rs @@ -51,56 +51,96 @@ impl FilterAvm2Ext for Filter { activation: &mut Activation<'_, 'gc>, object: Object<'gc>, ) -> Result> { - let bevel_filter = activation.avm2().classes().bevelfilter; + let bevel_filter = activation + .avm2() + .classes() + .bevelfilter + .inner_class_definition(); if object.is_of_type(bevel_filter, &mut activation.context) { return avm2_to_bevel_filter(activation, object); } - let blur_filter = activation.avm2().classes().blurfilter; + let blur_filter = activation + .avm2() + .classes() + .blurfilter + .inner_class_definition(); if object.is_of_type(blur_filter, &mut activation.context) { return avm2_to_blur_filter(activation, object); } - let color_matrix_filter = activation.avm2().classes().colormatrixfilter; + let color_matrix_filter = activation + .avm2() + .classes() + .colormatrixfilter + .inner_class_definition(); if object.is_of_type(color_matrix_filter, &mut activation.context) { return avm2_to_color_matrix_filter(activation, object); } - let convolution_filter = activation.avm2().classes().convolutionfilter; + let convolution_filter = activation + .avm2() + .classes() + .convolutionfilter + .inner_class_definition(); if object.is_of_type(convolution_filter, &mut activation.context) { return avm2_to_convolution_filter(activation, object); } - let displacement_map_filter = activation.avm2().classes().displacementmapfilter; + let displacement_map_filter = activation + .avm2() + .classes() + .displacementmapfilter + .inner_class_definition(); if object.is_of_type(displacement_map_filter, &mut activation.context) { return avm2_to_displacement_map_filter(activation, object); } - let drop_shadow_filter = activation.avm2().classes().dropshadowfilter; + let drop_shadow_filter = activation + .avm2() + .classes() + .dropshadowfilter + .inner_class_definition(); if object.is_of_type(drop_shadow_filter, &mut activation.context) { return avm2_to_drop_shadow_filter(activation, object); } - let glow_filter = activation.avm2().classes().glowfilter; + let glow_filter = activation + .avm2() + .classes() + .glowfilter + .inner_class_definition(); if object.is_of_type(glow_filter, &mut activation.context) { return avm2_to_glow_filter(activation, object); } - let gradient_bevel_filter = activation.avm2().classes().gradientbevelfilter; + let gradient_bevel_filter = activation + .avm2() + .classes() + .gradientbevelfilter + .inner_class_definition(); if object.is_of_type(gradient_bevel_filter, &mut activation.context) { return Ok(Filter::GradientBevelFilter(avm2_to_gradient_filter( activation, object, )?)); } - let gradient_glow_filter = activation.avm2().classes().gradientglowfilter; + let gradient_glow_filter = activation + .avm2() + .classes() + .gradientglowfilter + .inner_class_definition(); if object.is_of_type(gradient_glow_filter, &mut activation.context) { return Ok(Filter::GradientGlowFilter(avm2_to_gradient_filter( activation, object, )?)); } - let shader_filter = activation.avm2().classes().shaderfilter; + let shader_filter = activation + .avm2() + .classes() + .shaderfilter + .inner_class_definition(); if object.is_of_type(shader_filter, &mut activation.context) { return Ok(Filter::ShaderFilter(avm2_to_shader_filter( activation, object, diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index cbae3112d..f0e102d7a 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -700,8 +700,12 @@ pub fn hit_test<'gc>( ); let source_threshold = args.get_u32(activation, 1)?.clamp(0, u8::MAX.into()) as u8; let compare_object = args.get_object(activation, 2, "secondObject")?; - let point_class = activation.avm2().classes().point; - let rectangle_class = activation.avm2().classes().rectangle; + let point_class = activation.avm2().classes().point.inner_class_definition(); + let rectangle_class = activation + .avm2() + .classes() + .rectangle + .inner_class_definition(); if compare_object.is_of_type(point_class, &mut activation.context) { let test_point = ( diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index b1058fc13..0b14359d4 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -264,7 +264,9 @@ pub fn set_filters<'gc>( Namespace::package("flash.filters", &mut activation.borrow_gc()); let filter_class = Multiname::new(filters_namespace, "BitmapFilter"); - let filter_class_object = activation.resolve_class(&filter_class)?; + let filter_class_object = activation + .resolve_class(&filter_class)? + .inner_class_definition(); let mut filter_vec = Vec::with_capacity(filters_storage.length()); for filter in filters_storage.iter().flatten() { diff --git a/core/src/avm2/globals/flash/display/display_object_container.rs b/core/src/avm2/globals/flash/display/display_object_container.rs index cb5582aee..de288f463 100644 --- a/core/src/avm2/globals/flash/display/display_object_container.rs +++ b/core/src/avm2/globals/flash/display/display_object_container.rs @@ -176,7 +176,7 @@ pub fn add_child<'gc>( if let Some(ctr) = parent.as_container() { let child = args.get_object(activation, 0, "child")?; if child.as_display_object().is_none() { - let sprite = activation.avm2().classes().sprite; + let sprite = activation.avm2().classes().sprite.inner_class_definition(); if child.is_of_type(sprite, &mut activation.context) { // [NA] Hack to make Haxe work - they call addChild before super() // This will create an empty sprite the same way sprite's constructor will. diff --git a/core/src/avm2/globals/flash/display3D/context_3d.rs b/core/src/avm2/globals/flash/display3D/context_3d.rs index ae125ea33..fe55c8f70 100644 --- a/core/src/avm2/globals/flash/display3D/context_3d.rs +++ b/core/src/avm2/globals/flash/display3D/context_3d.rs @@ -504,7 +504,11 @@ pub fn set_texture_at<'gc>( } else { let obj = args[1].coerce_to_object(activation)?; cube = obj.is_of_type( - activation.avm2().classes().cubetexture, + activation + .avm2() + .classes() + .cubetexture + .inner_class_definition(), &mut activation.context, ); Some(obj.as_texture().unwrap().handle()) diff --git a/core/src/avm2/globals/json.rs b/core/src/avm2/globals/json.rs index 5f99ac9de..a68ac7cfe 100644 --- a/core/src/avm2/globals/json.rs +++ b/core/src/avm2/globals/json.rs @@ -235,13 +235,15 @@ impl<'gc> AvmSerializer<'gc> { )?)); } self.obj_stack.push(obj); - let value = - if obj.is_of_type(activation.avm2().classes().array, &mut activation.context) { - // TODO: Vectors - self.serialize_iterable(activation, obj)? - } else { - self.serialize_object(activation, obj)? - }; + let value = if obj.is_of_type( + activation.avm2().classes().array.inner_class_definition(), + &mut activation.context, + ) { + // TODO: Vectors + self.serialize_iterable(activation, obj)? + } else { + self.serialize_object(activation, obj)? + }; self.obj_stack .pop() .expect("Stack underflow during JSON serialization"); diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index f8352ab00..5458981bb 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -291,7 +291,7 @@ fn match_s<'gc>( let this = Value::from(this).coerce_to_string(activation)?; let regexp_class = activation.avm2().classes().regexp; - let pattern = if !pattern.is_of_type(activation, regexp_class) { + let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) { let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? } else { @@ -401,7 +401,7 @@ fn search<'gc>( let this = Value::from(this).coerce_to_string(activation)?; let regexp_class = activation.avm2().classes().regexp; - let pattern = if !pattern.is_of_type(activation, regexp_class) { + let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) { let string = pattern.coerce_to_string(activation)?; regexp_class.construct(activation, &[Value::String(string)])? } else { diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 4410333f8..1ce383e9b 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -77,7 +77,7 @@ fn class_call<'gc>( while let Some(r) = iter.next(activation) { let (_, item) = r?; - let coerced_item = item.coerce_to_type(activation, value_type)?; + let coerced_item = item.coerce_to_type(activation, value_type.inner_class_definition())?; new_storage.push(coerced_item, activation)?; } @@ -310,7 +310,7 @@ pub fn concat<'gc>( let my_class = this .instance_of() .ok_or("TypeError: Tried to concat into a bare object")?; - let val_class = new_vector_storage.value_type(); + let val_class = new_vector_storage.value_type().inner_class_definition(); for arg in args { let arg_obj = arg @@ -319,7 +319,7 @@ 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) { + if !arg.is_of_type(activation, my_class.inner_class_definition()) { return Err(format!( "TypeError: Cannot coerce argument of type {:?} to argument of type {:?}", arg_class.read().name(), @@ -344,7 +344,7 @@ pub fn concat<'gc>( return Err(format!( "TypeError: Cannot coerce Vector value of type {:?} to type {:?}", other_val_class.read().name(), - val_class.inner_class_definition().read().name() + val_class.read().name() ) .into()); } @@ -641,7 +641,8 @@ pub fn map<'gc>( let (i, item) = r?; let new_item = callback.call(receiver, &[item, i.into(), this.into()], activation)?; - let coerced_item = new_item.coerce_to_type(activation, value_type)?; + let coerced_item = + new_item.coerce_to_type(activation, value_type.inner_class_definition())?; new_storage.push(coerced_item, activation)?; } @@ -669,7 +670,7 @@ pub fn push<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) { - let value_type = vs.value_type(); + let value_type = vs.value_type().inner_class_definition(); for arg in args { let coerced_arg = arg.coerce_to_type(activation, value_type)?; @@ -703,7 +704,7 @@ pub fn unshift<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) { - let value_type = vs.value_type(); + let value_type = vs.value_type().inner_class_definition(); for arg in args.iter().rev() { let coerced_arg = arg.coerce_to_type(activation, value_type)?; @@ -729,7 +730,7 @@ pub fn insert_at<'gc>( .cloned() .unwrap_or(Value::Undefined) .coerce_to_i32(activation)?; - let value_type = vs.value_type(); + let value_type = vs.value_type().inner_class_definition(); let value = args .get(1) .cloned() @@ -930,7 +931,7 @@ pub fn splice<'gc>( let mut to_coerce = Vec::new(); for value in args[2..].iter() { - to_coerce.push(value.coerce_to_type(activation, value_type)?); + to_coerce.push(value.coerce_to_type(activation, value_type.inner_class_definition())?); } let new_vs = diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 66af53cc8..d78dac5b3 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1062,14 +1062,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// checking against this object. fn is_of_type( &self, - test_class: ClassObject<'gc>, + test_class: GcCell<'gc, Class<'gc>>, context: &mut UpdateContext<'_, 'gc>, ) -> bool { let my_class = self.instance_of(); // ES3 objects are not class instances but are still treated as // instances of Object, which is an ES4 class. - if my_class.is_none() && Object::ptr_eq(test_class, context.avm2.classes().object) { + if my_class.is_none() + && GcCell::ptr_eq( + test_class, + context.avm2.classes().object.inner_class_definition(), + ) + { true } else if let Some(my_class) = my_class { my_class.has_class_in_chain(test_class) diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index f35b9238d..ffeed1187 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -329,7 +329,11 @@ impl<'gc> ClassObject<'gc> { let mut queue = vec![class]; while let Some(cls) = queue.pop() { for interface_name in cls.read().direct_interfaces() { - let interface = self.early_resolve_class(scope.domain(), interface_name)?; + let interface = self.early_resolve_class( + scope.domain(), + interface_name, + activation.context.gc_context, + )?; if !interface.read().is_interface() { return Err(format!( @@ -346,7 +350,11 @@ impl<'gc> ClassObject<'gc> { } if let Some(superclass_name) = cls.read().super_class_name() { - queue.push(self.early_resolve_class(scope.domain(), superclass_name)?); + queue.push(self.early_resolve_class( + scope.domain(), + superclass_name, + activation.context.gc_context, + )?); } } write.interfaces = interfaces; @@ -385,9 +393,10 @@ impl<'gc> ClassObject<'gc> { &self, domain: Domain<'gc>, class_name: &Multiname<'gc>, + mc: MutationContext<'gc, '_>, ) -> Result>, Error<'gc>> { domain - .get_class(class_name)? + .get_class(class_name, mc)? .ok_or_else(|| format!("Could not resolve class {class_name:?}").into()) } @@ -448,24 +457,19 @@ impl<'gc> ClassObject<'gc> { /// 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: ClassObject<'gc>) -> bool { + pub fn has_class_in_chain(self, test_class: GcCell<'gc, Class<'gc>>) -> bool { let mut my_class = Some(self); while let Some(class) = my_class { - if Object::ptr_eq(class, test_class) { + if GcCell::ptr_eq(class.inner_class_definition(), test_class) { return true; } - if let (Some(my_param), Some(test_param)) = - (class.as_class_params(), test_class.as_class_params()) + 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()) { - let are_all_params_coercible = match (my_param, test_param) { - (Some(my_param), Some(test_param)) => my_param.has_class_in_chain(test_param), - (None, Some(_)) => false, - _ => true, - }; - - if are_all_params_coercible { + if my_param.has_class_in_chain(*other_single_param) { return true; } } @@ -478,9 +482,9 @@ impl<'gc> ClassObject<'gc> { // 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.inner_class_definition().read().is_interface() { + if test_class.read().is_interface() { for interface in self.interfaces() { - if GcCell::ptr_eq(interface, test_class.inner_class_definition()) { + if GcCell::ptr_eq(interface, test_class) { return true; } } @@ -851,7 +855,7 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { .get(0) .cloned() .unwrap_or(Value::Undefined) - .coerce_to_type(activation, self) + .coerce_to_type(activation, self.inner_class_definition()) } } @@ -933,9 +937,8 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> { .unwrap_or(activation.avm2().classes().object) .inner_class_definition(); - let parameterized_class = self_class - .read() - .with_type_param(class_param, activation.context.gc_context); + 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; diff --git a/core/src/avm2/object/vector_object.rs b/core/src/avm2/object/vector_object.rs index 4011e7787..673a1f229 100644 --- a/core/src/avm2/object/vector_object.rs +++ b/core/src/avm2/object/vector_object.rs @@ -133,7 +133,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> { if name.contains_public_namespace() { if let Some(name) = name.local_name() { if let Ok(index) = name.parse::() { - let type_of = self.0.read().vector.value_type(); + let type_of = self.0.read().vector.value_type().inner_class_definition(); let value = match value.coerce_to_type(activation, type_of)? { Value::Undefined => self.0.read().vector.default(activation), Value::Null => self.0.read().vector.default(activation), @@ -164,7 +164,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> { if name.contains_public_namespace() { if let Some(name) = name.local_name() { if let Ok(index) = name.parse::() { - let type_of = self.0.read().vector.value_type(); + let type_of = self.0.read().vector.value_type().inner_class_definition(); let value = match value.coerce_to_type(activation, type_of)? { Value::Undefined => self.0.read().vector.default(activation), Value::Null => self.0.read().vector.default(activation), diff --git a/core/src/avm2/property.rs b/core/src/avm2/property.rs index 31e390406..f2fd6b633 100644 --- a/core/src/avm2/property.rs +++ b/core/src/avm2/property.rs @@ -1,15 +1,16 @@ //! Property data structures -use crate::avm2::object::TObject; use crate::avm2::Activation; -use crate::avm2::ClassObject; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::TranslationUnit; use crate::avm2::Value; +use gc_arena::GcCell; use gc_arena::MutationContext; use gc_arena::{Collect, Gc}; +use super::class::Class; + #[derive(Debug, Collect, Clone, Copy)] #[collect(no_drop)] pub enum Property { @@ -39,7 +40,7 @@ pub enum PropertyClass<'gc> { /// `Value::Undefined`, so it needs to be distinguished /// from the `Object` class Any, - Class(ClassObject<'gc>), + Class(GcCell<'gc, Class<'gc>>), Name(Gc<'gc, (Multiname<'gc>, Option>)>), } @@ -64,56 +65,28 @@ impl<'gc> PropertyClass<'gc> { PropertyClass::Class(class) => (Some(*class), false), PropertyClass::Name(gc) => { let (name, unit) = &**gc; - let outcome = resolve_class_private(name, *unit, activation)?; - let class = match outcome { - ResolveOutcome::Class(class) => { + if name.is_any_name() { + *self = PropertyClass::Any; + (None, true) + } else { + // Note - we look up the class in the domain by name, which allows us to look up private classes. + // This also has the advantage of letting us coerce to a class while the `ClassObject` + // is still being constructed (since the `Class` will already exist in the domain). + + // We should only be missing a translation unit when performing a lookup from playerglobals, + // so use that domain if we don't have a translation unit. + let domain = + unit.map_or(activation.avm2().playerglobals_domain, |u| u.domain()); + if let Some(class) = domain.get_class(name, activation.context.gc_context)? { *self = PropertyClass::Class(class); (Some(class), true) + } else { + return Err(format!( + "Could not resolve class {name:?} for property coercion" + ) + .into()); } - ResolveOutcome::Any => { - *self = PropertyClass::Any; - (None, true) - } - ResolveOutcome::NotFound => { - // FP allows a class to reference its own type in a static initializer: - // `class Foo { static var INSTANCE: Foo = new Foo(); }` - // When this happens, the `ClassObject` for `Foo` will not yet - // be available when we perform the coercion, since we're still - // in the process of constructing it. - // - // Fortunately, a coercion to a non-primitive class either - // succeeds with the value unchanged, or fails (if the object - // is not an instance of the class). Therefore, we can just check - // if the class name and domain match our property name, without - // actually needing to perform resolution. This does not handle subclasses, - // but that's fine - a superclass cannot reference a subclass from a class - // initializer. - // - // We should eventually be able to remove this when we refactor - // `Class`/`ClassObject` to be closer to what avmplus does. - if let Some(object) = value.as_object() { - if let Some(class) = object.instance_of() { - if name.contains_name(&class.inner_class_definition().read().name()) - && unit.map(|u| u.domain()) - == Some(class.class_scope().domain()) - { - // Even though resolution succeeded, we haven't modified - // this `PropertyClass`, so return `false` - return Ok((value, false)); - } - } - } else if matches!(value, Value::Null) || matches!(value, Value::Undefined) - { - //AVM2 properties are nullable, so null is always an instance of the class - return Ok((Value::Null, false)); - } - - return Err(Error::from(format!( - "Attempted to perform (private) resolution of nonexistent type {name:?}", - ))); - } - }; - class + } } PropertyClass::Any => (None, false), }; @@ -129,58 +102,13 @@ impl<'gc> PropertyClass<'gc> { pub fn get_name(&self, mc: MutationContext<'gc, '_>) -> Multiname<'gc> { match self { - PropertyClass::Class(class) => class.inner_class_definition().read().name().into(), + PropertyClass::Class(class) => class.read().name().into(), PropertyClass::Name(gc) => gc.0.clone(), PropertyClass::Any => Multiname::any(mc), } } } -enum ResolveOutcome<'gc> { - Class(ClassObject<'gc>), - Any, - NotFound, -} - -/// Resolves a class definition referenced by the type of a property. -/// This supports private (`Namespace::Private`) classes, -/// and does not use the `ScopeStack`/`ScopeChain`. -/// -/// This is an internal operation used to resolve property type names. -/// It does not correspond to any opcode or native method. -fn resolve_class_private<'gc>( - name: &Multiname<'gc>, - unit: Option>, - activation: &mut Activation<'_, 'gc>, -) -> Result, Error<'gc>> { - // A Property may have a type of '*' (which corresponds to 'Multiname::any()') - // We don't want to perform any coercions in this case - in particular, - // this means that the property can have a value of `Undefined`. - // If the type is `Object`, then a value of `Undefind` gets coerced - // to `Null` - if name.is_any_name() { - Ok(ResolveOutcome::Any) - } else { - // First, check the domain for an exported (non-private) class. - // If the property we're resolving for lacks a `TranslationUnit`, - // use the stage domain - let domain = unit.map_or(activation.avm2().stage_domain, |u| u.domain()); - let globals = if let Some((_, mut script)) = domain.get_defining_script(name)? { - script.globals(&mut activation.context)? - } else if unit.is_some() { - return Err(format!("Could not find script for class trait {name:?}").into()); - } else { - return Err(format!("Missing script and translation unit for class {name:?}").into()); - }; - - Ok(globals - .get_property(name, activation)? - .as_object() - .and_then(|o| o.as_class_object()) - .map_or(ResolveOutcome::NotFound, ResolveOutcome::Class)) - } -} - impl Property { pub fn new_method(disp_id: u32) -> Self { Property::Method { disp_id } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 241dcc621..6f46cc01e 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -3,7 +3,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error; use crate::avm2::error::type_error; -use crate::avm2::object::{ClassObject, NamespaceObject, Object, PrimitiveObject, TObject}; +use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject}; use crate::avm2::script::TranslationUnit; use crate::avm2::Error; use crate::avm2::Multiname; @@ -16,6 +16,7 @@ use std::cell::Ref; use std::mem::size_of; use swf::avm2::types::{DefaultValue as AbcDefaultValue, Index}; +use super::class::Class; use super::e4x::E4XNode; /// Indicate what kind of primitive coercion would be preferred when coercing @@ -957,44 +958,19 @@ impl<'gc> Value<'gc> { activation: &mut Activation<'_, 'gc>, type_name: &Multiname<'gc>, ) -> Result, Error<'gc>> { - let param_type = match activation.resolve_type(type_name) { - Ok(param_type) => param_type, - Err(e) => { - // While running a class initializer, we might need to resolve the class - // itself. For example, a static/const can run a static method, which does - // `var foo:ClassBeingInitialized = new ClassBeingInitialized();` - // - // Since the class initializer is running, we won't be able to resolve the - // ClassObject yet. If the normal `resolve_type` lookup fails, then - // try resolving the class through `domain().get_class`, and check if the - // object's class matches directly (not considering superclasses or interfaces). - // Any superclasses or superinterfaces will already have been initialized, - // so the `resolve_type` lookup will succeed for them. - - if let Ok(Some(resolved_class)) = activation.domain().get_class(type_name) { - // Note that we do this check *after* successfully resolving the class. This ensures - // that we still produce errors when trying to coerce null/undefined to a completely - // non-existent class. - if matches!(self, Value::Undefined) || matches!(self, Value::Null) { - return Ok(Value::Null); - } - if let Some(obj) = self.as_object() { - if let Some(obj_class) = obj.instance_of_class_definition() { - if GcCell::ptr_eq(resolved_class, obj_class) { - return Ok(*self); - } - } - } - } - return Err(e); - } - }; - - if let Some(param_type) = param_type { - self.coerce_to_type(activation, param_type) - } else { - Ok(*self) + if type_name.is_any_name() { + return Ok(*self); } + let param_type = activation + .domain() + .get_class(type_name, activation.context.gc_context)? + .ok_or_else(|| { + Error::RustError( + format!("Failed to lookup class {:?} during coercion", type_name).into(), + ) + })?; + + self.coerce_to_type(activation, param_type) } /// Coerce the value to another value by type name. @@ -1008,21 +984,33 @@ impl<'gc> Value<'gc> { pub fn coerce_to_type( &self, activation: &mut Activation<'_, 'gc>, - class: ClassObject<'gc>, + class: GcCell<'gc, Class<'gc>>, ) -> Result, Error<'gc>> { - if Object::ptr_eq(class, activation.avm2().classes().int) { + if GcCell::ptr_eq( + class, + activation.avm2().classes().int.inner_class_definition(), + ) { return Ok(self.coerce_to_i32(activation)?.into()); } - if Object::ptr_eq(class, activation.avm2().classes().uint) { + if GcCell::ptr_eq( + class, + activation.avm2().classes().uint.inner_class_definition(), + ) { return Ok(self.coerce_to_u32(activation)?.into()); } - if Object::ptr_eq(class, activation.avm2().classes().number) { + if GcCell::ptr_eq( + class, + activation.avm2().classes().number.inner_class_definition(), + ) { return Ok(self.coerce_to_number(activation)?.into()); } - if Object::ptr_eq(class, activation.avm2().classes().boolean) { + if GcCell::ptr_eq( + class, + activation.avm2().classes().boolean.inner_class_definition(), + ) { return Ok(self.coerce_to_boolean().into()); } @@ -1030,7 +1018,10 @@ impl<'gc> Value<'gc> { return Ok(Value::Null); } - if Object::ptr_eq(class, activation.avm2().classes().string) { + if GcCell::ptr_eq( + class, + activation.avm2().classes().string.inner_class_definition(), + ) { return Ok(self.coerce_to_string(activation)?.into()); } @@ -1040,7 +1031,7 @@ impl<'gc> Value<'gc> { } if let Some(vector) = object.as_vector_storage() { - let name = class.inner_class_definition().read().name(); + 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") @@ -1059,7 +1050,6 @@ impl<'gc> Value<'gc> { } let name = class - .inner_class_definition() .read() .name() .to_qualified_name_err_message(activation.context.gc_context); @@ -1128,15 +1118,24 @@ impl<'gc> Value<'gc> { pub fn is_of_type( &self, activation: &mut Activation<'_, 'gc>, - type_object: ClassObject<'gc>, + type_object: GcCell<'gc, Class<'gc>>, ) -> bool { - if Object::ptr_eq(type_object, activation.avm2().classes().number) { + if GcCell::ptr_eq( + type_object, + activation.avm2().classes().number.inner_class_definition(), + ) { return self.is_number(); } - if Object::ptr_eq(type_object, activation.avm2().classes().uint) { + if GcCell::ptr_eq( + type_object, + activation.avm2().classes().uint.inner_class_definition(), + ) { return self.is_u32(); } - if Object::ptr_eq(type_object, activation.avm2().classes().int) { + if GcCell::ptr_eq( + type_object, + activation.avm2().classes().int.inner_class_definition(), + ) { return self.is_i32(); } diff --git a/core/src/avm2/vtable.rs b/core/src/avm2/vtable.rs index 32aadf7a1..d13c21fbd 100644 --- a/core/src/avm2/vtable.rs +++ b/core/src/avm2/vtable.rs @@ -407,7 +407,13 @@ impl<'gc> VTable<'gc> { ), TraitKind::Function { .. } => ( Property::new_slot(new_slot_id), - PropertyClass::Class(activation.avm2().classes().function), + PropertyClass::Class( + activation + .avm2() + .classes() + .function + .inner_class_definition(), + ), ), TraitKind::Const { type_name, unit, .. @@ -421,7 +427,9 @@ impl<'gc> VTable<'gc> { ), TraitKind::Class { .. } => ( Property::new_const_slot(new_slot_id), - PropertyClass::Class(activation.avm2().classes().class), + PropertyClass::Class( + activation.avm2().classes().class.inner_class_definition(), + ), ), _ => unreachable!(), }; @@ -486,7 +494,9 @@ impl<'gc> VTable<'gc> { write .resolved_traits .insert(name, Property::new_slot(new_slot_id)); - write.slot_classes.push(PropertyClass::Class(class)); + write + .slot_classes + .push(PropertyClass::Class(class.inner_class_definition())); new_slot_id } diff --git a/core/src/display_object.rs b/core/src/display_object.rs index d46dc8dc4..f0c6e95c0 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1708,7 +1708,7 @@ pub trait TDisplayObject<'gc>: .object2() .as_object() .expect("MovieClip object should have been constructed"); - let movieclip_class = context.avm2.classes().movieclip; + let movieclip_class = context.avm2.classes().movieclip.inner_class_definition(); // It's possible to have a DefineSprite tag with multiple frames, but have // the corresponding `SymbolClass` *not* extend `MovieClip` (e.g. extending `Sprite` directly.) // When this occurs, Flash Player will run the first frame, and immediately stop. diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index b5e31a135..0acb1ad58 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -219,9 +219,13 @@ impl<'gc> Bitmap<'gc> { context: &mut UpdateContext<'_, 'gc>, class: Avm2ClassObject<'gc>, ) { - let bitmap_class = if class.has_class_in_chain(context.avm2.classes().bitmap) { + let bitmap_class = if class + .has_class_in_chain(context.avm2.classes().bitmap.inner_class_definition()) + { BitmapClass::Bitmap(class) - } else if class.has_class_in_chain(context.avm2.classes().bitmapdata) { + } else if class + .has_class_in_chain(context.avm2.classes().bitmapdata.inner_class_definition()) + { BitmapClass::BitmapData(class) } else { return tracing::error!("Associated class {:?} for symbol {} must extend flash.display.Bitmap or BitmapData, does neither", class.inner_class_definition().read().name(), self.id()); diff --git a/tests/tests/swfs/avm2/vector_coercion/Test.as b/tests/tests/swfs/avm2/vector_coercion/Test.as index 4099fe572..08409d5af 100644 --- a/tests/tests/swfs/avm2/vector_coercion/Test.as +++ b/tests/tests/swfs/avm2/vector_coercion/Test.as @@ -171,4 +171,16 @@ trace("/// c_vector[1] = b_vector;"); c_vector[1] = b_vector; trace("/// (contents of c_vector...)"); -trace_vector(c_vector); \ No newline at end of file +trace_vector(c_vector); + +class MyObject {} + + +var myobj_vec: Vector. = new []; +try { + var cast: Vector. = myobj_vec; +} catch (e) { + // Replace the non-deterministic address value with a placeholder string. + var normalized = e.toString().replace(/@[0-9A-Fa-f]+/, "@ADDRESS") + trace("Caught error: " + normalized); +} diff --git a/tests/tests/swfs/avm2/vector_coercion/output.txt b/tests/tests/swfs/avm2/vector_coercion/output.txt index 903db33e1..72f950502 100644 --- a/tests/tests/swfs/avm2/vector_coercion/output.txt +++ b/tests/tests/swfs/avm2/vector_coercion/output.txt @@ -63,3 +63,4 @@ true /// (contents of c_vector...) 1,2 5,16 +Caught error: TypeError: Error #1034: Type Coercion failed: cannot convert __AS3__.vec::Vector.@ADDRESS to __AS3__.vec.Vector.. diff --git a/tests/tests/swfs/avm2/vector_coercion/test.swf b/tests/tests/swfs/avm2/vector_coercion/test.swf index 23197c00b..5b4c753af 100644 Binary files a/tests/tests/swfs/avm2/vector_coercion/test.swf and b/tests/tests/swfs/avm2/vector_coercion/test.swf differ