From 9c872860e98bff1683c494b7a364c19b930478b7 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Wed, 19 Jun 2024 16:29:07 -0700 Subject: [PATCH] avm2: Actually apply class refactor to ClassObject and describeType This requires changing the AVM2 ClassObject symbol registry to use Classes --- core/src/avm2/class.rs | 4 +- core/src/avm2/globals.rs | 14 +++- core/src/avm2/globals/avmplus.rs | 82 +++++++------------ core/src/avm2/globals/class.rs | 3 +- core/src/avm2/globals/flash/display/bitmap.rs | 8 +- .../avm2/globals/flash/display/bitmap_data.rs | 2 +- core/src/avm2/globals/flash/display/shape.rs | 8 +- .../globals/flash/display/simple_button.rs | 12 ++- core/src/avm2/globals/flash/display/sprite.rs | 8 +- core/src/avm2/globals/flash/media/sound.rs | 8 +- core/src/avm2/globals/flash/media/video.rs | 6 +- core/src/avm2/globals/flash/text/font.rs | 2 +- .../src/avm2/globals/flash/text/text_field.rs | 12 ++- core/src/avm2/globals/function.rs | 3 +- core/src/avm2/globals/object.rs | 3 +- core/src/avm2/object.rs | 15 +--- core/src/avm2/object/bytearray_object.rs | 2 +- core/src/avm2/object/class_object.rs | 28 ++----- core/src/avm2/object/font_object.rs | 2 +- core/src/avm2/object/script_object.rs | 19 ++--- core/src/avm2/optimize.rs | 39 ++++----- core/src/avm2/script.rs | 17 +++- core/src/debug_ui/avm2.rs | 2 +- core/src/display_object/movie_clip.rs | 6 +- core/src/library.rs | 22 ++--- 25 files changed, 153 insertions(+), 174 deletions(-) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 6c61d5f5a..71ba6b495 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -564,7 +564,7 @@ impl<'gc> Class<'gc> { param: None, super_class: Some(activation.avm2().classes().class.inner_class_definition()), attributes: ClassAttributes::FINAL, - protected_namespace: None, + protected_namespace, direct_interfaces: Vec::new(), all_interfaces: Vec::new(), instance_allocator: Allocator(scriptobject_allocator), @@ -944,7 +944,7 @@ impl<'gc> Class<'gc> { // Matching avmplus, this doesn't check whether the class is a // c_class; it strips the suffix even for i_classes - if let Some(stripped) = local_name_wstr.strip_suffix('$' as u8) { + if let Some(stripped) = local_name_wstr.strip_suffix(b'$') { let new_local_name = AvmString::new(mc, stripped); QName::new(namespace, new_local_name) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 1286734c0..039989aeb 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -519,15 +519,19 @@ pub fn load_player_globals<'gc>( let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto)); domain.export_class(fn_classdef.name(), fn_classdef, mc); + let fn_c_class = fn_classdef + .c_class() + .expect("function::create_class returns an i_class"); + // Now to weave the Gordian knot... object_class.link_prototype(activation, object_proto)?; - object_class.link_type(mc, class_proto, class_class); + object_class.link_type(mc, class_proto, object_c_class); fn_class.link_prototype(activation, fn_proto)?; - fn_class.link_type(mc, class_proto, class_class); + fn_class.link_type(mc, class_proto, fn_c_class); class_class.link_prototype(activation, class_proto)?; - class_class.link_type(mc, class_proto, class_class); + class_class.link_type(mc, class_proto, class_c_class); // At this point, we need at least a partial set of system classes in // order to continue initializing the player. The rest of the classes @@ -561,11 +565,13 @@ pub fn load_player_globals<'gc>( let global_class = ClassObject::from_class(activation, global_classdef, Some(object_class))?; globals.set_proto(mc, global_class.prototype()); - globals.set_instance_of(mc, global_class); + globals.set_instance_class(mc, global_classdef); + globals.set_vtable(mc, global_class.instance_vtable()); activation.context.avm2.toplevel_global_object = Some(globals); script.set_global_class(mc, global_classdef); + script.set_global_class_obj(mc, global_class); // From this point, `globals` is safe to be modified diff --git a/core/src/avm2/globals/avmplus.rs b/core/src/avm2/globals/avmplus.rs index da77a208b..1d8520c3c 100644 --- a/core/src/avm2/globals/avmplus.rs +++ b/core/src/avm2/globals/avmplus.rs @@ -1,12 +1,12 @@ +use crate::avm2::class::Class; pub use crate::avm2::globals::flash::utils::get_qualified_class_name; use crate::avm2::metadata::Metadata; use crate::avm2::method::Method; use crate::avm2::object::{ArrayObject, TObject}; use crate::avm2::parameters::ParametersExt; use crate::avm2::property::Property; -use crate::avm2::{ClassObject, Namespace}; -use crate::avm2::{Activation, Error, Object, Value}; +use crate::avm2::{Activation, Error, Namespace, Object, Value}; use crate::avm2_stub_method; // Implements `avmplus.describeTypeJSON` @@ -21,42 +21,44 @@ pub fn describe_type_json<'gc>( } let value = args[0].coerce_to_object(activation)?; - let class_obj = value.as_class_object().or_else(|| value.instance_of()); + let class_def = value.instance_class(); let object = activation .avm2() .classes() .object .construct(activation, &[])?; - let Some(class_obj) = class_obj else { + let Some(class_def) = class_def else { return Ok(Value::Null); }; - let is_static = value.as_class_object().is_some(); - if !is_static && flags.contains(DescribeTypeFlags::USE_ITRAITS) { - return Ok(Value::Null); + let mut used_class_def = class_def; + if flags.contains(DescribeTypeFlags::USE_ITRAITS) { + if let Some(i_class) = used_class_def.i_class() { + used_class_def = i_class; + } else { + return Ok(Value::Null); + } } - let class = class_obj.inner_class_definition(); - - let qualified_name = class - .name() + let qualified_name = used_class_def + .dollar_removed_name(activation.context.gc_context) .to_qualified_name(activation.context.gc_context); object.set_public_property("name", qualified_name.into(), activation)?; object.set_public_property( "isDynamic", - (is_static || !class.is_sealed()).into(), + (!used_class_def.is_sealed()).into(), activation, )?; + object.set_public_property("isFinal", used_class_def.is_final().into(), activation)?; object.set_public_property( - "isFinal", - (is_static || class.is_final()).into(), + "isStatic", + value.as_class_object().is_some().into(), activation, )?; - object.set_public_property("isStatic", is_static.into(), activation)?; - let traits = describe_internal_body(activation, class_obj, is_static, flags)?; + let traits = describe_internal_body(activation, used_class_def, flags)?; if flags.contains(DescribeTypeFlags::INCLUDE_TRAITS) { object.set_public_property("traits", traits.into(), activation)?; } else { @@ -181,14 +183,9 @@ fn describe_type_json_null<'gc>( fn describe_internal_body<'gc>( activation: &mut Activation<'_, 'gc>, - class_obj: ClassObject<'gc>, - is_static: bool, + class_def: Class<'gc>, flags: DescribeTypeFlags, ) -> Result, Error<'gc>> { - // If we were passed a non-ClassObject, or the caller specifically requested it, then - // look at the instance "traits" (our implementation is different than avmplus) - - let use_instance_traits = !is_static || flags.contains(DescribeTypeFlags::USE_ITRAITS); let traits = activation .avm2() .classes() @@ -247,39 +244,24 @@ fn describe_internal_body<'gc>( .as_array_storage_mut(activation.context.gc_context) .unwrap(); - let superclass = if use_instance_traits { - class_obj.superclass_object() - } else { - Some(activation.avm2().classes().class) - }; + let superclass = class_def.super_class(); if flags.contains(DescribeTypeFlags::INCLUDE_BASES) { - let mut current_super_obj = superclass; - while let Some(super_obj) = current_super_obj { - let super_name = super_obj - .inner_class_definition() + let mut current_super_class = superclass; + while let Some(super_class) = current_super_class { + let super_name = super_class .name() .to_qualified_name(activation.context.gc_context); bases_array.push(super_name.into()); - current_super_obj = super_obj.superclass_object(); + current_super_class = super_class.super_class(); } } - // When we're describing a Class object, we use the class vtable (which hides instance properties) - let vtable = if use_instance_traits { - class_obj.instance_vtable() - } else { - class_obj.class_vtable() - }; + let vtable = class_def.instance_vtable(); + let super_vtable = class_def.super_class().map(|c| c.instance_vtable()); - let super_vtable = if use_instance_traits { - class_obj.superclass_object().map(|c| c.instance_vtable()) - } else { - class_obj.instance_class().map(|c| c.instance_vtable()) - }; - - if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) && use_instance_traits { - for interface in &*class_obj.inner_class_definition().all_interfaces() { + if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) { + for interface in &*class_def.all_interfaces() { let interface_name = interface .name() .to_qualified_name(activation.context.gc_context); @@ -475,7 +457,7 @@ fn describe_internal_body<'gc>( let accessor_type = method_type.to_qualified_name_or_star(activation.context.gc_context); let declared_by = defining_class - .name() + .dollar_removed_name(activation.context.gc_context) .to_qualified_name(activation.context.gc_context); let accessor_obj = activation @@ -524,11 +506,9 @@ fn describe_internal_body<'gc>( } } - let constructor = class_obj.inner_class_definition().instance_init(); + let constructor = class_def.instance_init(); // Flash only shows a element if it has at least one parameter - if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR) - && use_instance_traits - && !constructor.signature().is_empty() + if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR) && !constructor.signature().is_empty() { let params = write_params(&constructor, activation)?; traits.set_public_property("constructor", params.into(), activation)?; diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index 2775b5952..4a369b5fa 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -1,7 +1,7 @@ //! `Class` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::class::Class; +use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::object::{Object, TObject}; use crate::avm2::value::Value; @@ -85,6 +85,7 @@ pub fn create_c_class<'gc>( Method::from_builtin(class_init, "", gc_context), gc_context, ); + class_c_class.set_attributes(gc_context, ClassAttributes::FINAL); // 'length' is a weird undocumented constant in Class. // We need to define it, since it shows up in 'describeType' diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 4b97a6cf6..c67892f65 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -19,12 +19,12 @@ pub fn bitmap_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let bitmap_cls = activation.avm2().classes().bitmap; + let bitmap_cls = activation.avm2().classes().bitmap.inner_class_definition(); let bitmapdata_cls = activation.context.avm2.classes().bitmapdata; - let mut class_object = Some(class); + let mut class_def = Some(class.inner_class_definition()); let orig_class = class; - while let Some(class) = class_object { + while let Some(class) = class_def { if class == bitmap_cls { let bitmap_data = BitmapDataWrapper::dummy(activation.context.gc_context); let display_object = Bitmap::new_with_bitmap_data( @@ -77,7 +77,7 @@ pub fn bitmap_allocator<'gc>( return Ok(obj); } } - class_object = class.superclass_object(); + class_def = class.super_class(); } unreachable!("A Bitmap subclass should have Bitmap in superclass chain"); } diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 0b0efcfe4..c22220bba 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -96,7 +96,7 @@ pub fn init<'gc>( // which makes custom classes see a disposed BitmapData before they call super() let name = this.instance_class().map(|c| c.name()); let character = this - .instance_of() + .instance_class() .and_then(|t| { activation .context diff --git a/core/src/avm2/globals/flash/display/shape.rs b/core/src/avm2/globals/flash/display/shape.rs index d04f1e72e..827c5baff 100644 --- a/core/src/avm2/globals/flash/display/shape.rs +++ b/core/src/avm2/globals/flash/display/shape.rs @@ -12,11 +12,11 @@ pub fn shape_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let shape_cls = activation.avm2().classes().shape; + let shape_cls = activation.avm2().classes().shape.inner_class_definition(); - let mut class_object = Some(class); + let mut class_def = Some(class.inner_class_definition()); let orig_class = class; - while let Some(class) = class_object { + while let Some(class) = class_def { if class == shape_cls { let display_object = Graphic::empty(&mut activation.context).into(); return initialize_for_allocator(activation, display_object, orig_class); @@ -36,7 +36,7 @@ pub fn shape_allocator<'gc>( return initialize_for_allocator(activation, child, orig_class); } - class_object = class.superclass_object(); + class_def = class.super_class(); } unreachable!("A Shape subclass should have Shape in superclass chain"); } diff --git a/core/src/avm2/globals/flash/display/simple_button.rs b/core/src/avm2/globals/flash/display/simple_button.rs index 9b4e70da3..e375db42a 100644 --- a/core/src/avm2/globals/flash/display/simple_button.rs +++ b/core/src/avm2/globals/flash/display/simple_button.rs @@ -19,11 +19,15 @@ pub fn simple_button_allocator<'gc>( ) -> Result, Error<'gc>> { use crate::vminterface::Instantiator; - let simplebutton_cls = activation.avm2().classes().simplebutton; + let simplebutton_cls = activation + .avm2() + .classes() + .simplebutton + .inner_class_definition(); - let mut class_object = Some(class); + let mut class_def = Some(class.inner_class_definition()); let orig_class = class; - while let Some(class) = class_object { + while let Some(class) = class_def { if class == simplebutton_cls { let button = Avm2Button::empty_button(&mut activation.context); // [NA] Buttons specifically need to PO'd @@ -48,7 +52,7 @@ pub fn simple_button_allocator<'gc>( return initialize_for_allocator(activation, child, orig_class); } - class_object = class.superclass_object(); + class_def = class.super_class(); } unreachable!("A SimpleButton subclass should have SimpleButton in superclass chain"); } diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 670c20fe1..03ff4e67f 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -14,11 +14,11 @@ pub fn sprite_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let sprite_cls = activation.avm2().classes().sprite; + let sprite_cls = activation.avm2().classes().sprite.inner_class_definition(); - let mut class_object = Some(class); + let mut class_def = Some(class.inner_class_definition()); let orig_class = class; - while let Some(class) = class_object { + while let Some(class) = class_def { if class == sprite_cls { let movie = activation.caller_movie_or_root(); let display_object = MovieClip::new(movie, activation.context.gc_context).into(); @@ -39,7 +39,7 @@ pub fn sprite_allocator<'gc>( return initialize_for_allocator(activation, child, orig_class); } - class_object = class.superclass_object(); + class_def = class.super_class(); } unreachable!("A Sprite subclass should have Sprite in superclass chain"); } diff --git a/core/src/avm2/globals/flash/media/sound.rs b/core/src/avm2/globals/flash/media/sound.rs index b7e449af9..61783144a 100644 --- a/core/src/avm2/globals/flash/media/sound.rs +++ b/core/src/avm2/globals/flash/media/sound.rs @@ -22,15 +22,15 @@ pub fn init<'gc>( args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(sound_object) = this.as_sound_object() { - let class_object = this - .instance_of() + let class_def = this + .instance_class() .ok_or("Attempted to construct Sound on a bare object.")?; if let Some((movie, symbol)) = activation .context .library .avm2_class_registry() - .class_symbol(class_object) + .class_symbol(class_def) { if let Some(Character::Sound(sound)) = activation .context @@ -41,7 +41,7 @@ pub fn init<'gc>( let sound = *sound; sound_object.set_sound(&mut activation.context, sound)?; } else { - tracing::warn!("Attempted to construct subclass of Sound, {}, which is associated with non-Sound character {}", class_object.inner_class_definition().name().local_name(), symbol); + tracing::warn!("Attempted to construct subclass of Sound, {}, which is associated with non-Sound character {}", class_def.name().local_name(), symbol); } } } diff --git a/core/src/avm2/globals/flash/media/video.rs b/core/src/avm2/globals/flash/media/video.rs index cdcf05f1a..d70f629af 100644 --- a/core/src/avm2/globals/flash/media/video.rs +++ b/core/src/avm2/globals/flash/media/video.rs @@ -7,9 +7,9 @@ pub fn video_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let video_class = activation.avm2().classes().video; + let video_class = activation.avm2().classes().video.inner_class_definition(); - let mut target_class = Some(class); + let mut target_class = Some(class.inner_class_definition()); while let Some(target) = target_class { if target == video_class { let movie = activation.caller_movie_or_root(); @@ -32,7 +32,7 @@ pub fn video_allocator<'gc>( return initialize_for_allocator(activation, child, class); } - target_class = target.superclass_object(); + target_class = target.super_class(); } unreachable!("A Video subclass should have Video in superclass chain"); diff --git a/core/src/avm2/globals/flash/text/font.rs b/core/src/avm2/globals/flash/text/font.rs index bf5e73398..b6e944e12 100644 --- a/core/src/avm2/globals/flash/text/font.rs +++ b/core/src/avm2/globals/flash/text/font.rs @@ -133,7 +133,7 @@ pub fn register_font<'gc>( .context .library .avm2_class_registry() - .class_symbol(class) + .class_symbol(class.inner_class_definition()) { if let Some(lib) = activation.context.library.library_for_movie(movie) { if let Some(Character::Font(font)) = lib.character_by_id(id) { diff --git a/core/src/avm2/globals/flash/text/text_field.rs b/core/src/avm2/globals/flash/text/text_field.rs index ccc6b4db3..1831740e1 100644 --- a/core/src/avm2/globals/flash/text/text_field.rs +++ b/core/src/avm2/globals/flash/text/text_field.rs @@ -17,11 +17,15 @@ pub fn text_field_allocator<'gc>( class: ClassObject<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let textfield_cls = activation.avm2().classes().textfield; + let textfield_cls = activation + .avm2() + .classes() + .textfield + .inner_class_definition(); - let mut class_object = Some(class); + let mut class_def = Some(class.inner_class_definition()); let orig_class = class; - while let Some(class) = class_object { + while let Some(class) = class_def { if class == textfield_cls { let movie = activation.caller_movie_or_root(); let display_object = @@ -43,7 +47,7 @@ pub fn text_field_allocator<'gc>( return initialize_for_allocator(activation, child, orig_class); } - class_object = class.superclass_object(); + class_def = class.super_class(); } unreachable!("A TextField subclass should have TextField in superclass chain"); } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index 256b28db4..5b5e48fc3 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -1,7 +1,7 @@ //! Function builtin and prototype use crate::avm2::activation::Activation; -use crate::avm2::class::Class; +use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::error::eval_error; use crate::avm2::globals::array::resolve_array_hole; use crate::avm2::method::{Method, NativeMethodImpl}; @@ -239,6 +239,7 @@ pub fn create_class<'gc>( Method::from_builtin(class_init, "", gc_context), gc_context, ); + function_c_class.set_attributes(gc_context, ClassAttributes::FINAL); function_i_class.set_c_class(gc_context, function_c_class); function_c_class.set_i_class(gc_context, function_i_class); diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 0da520eed..16a979b6e 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -1,7 +1,7 @@ //! Object builtin and prototype use crate::avm2::activation::Activation; -use crate::avm2::class::Class; +use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig}; use crate::avm2::object::{FunctionObject, Object, TObject}; use crate::avm2::traits::Trait; @@ -324,6 +324,7 @@ pub fn create_c_class<'gc>( Method::from_builtin(class_init, "", gc_context), gc_context, ); + object_c_class.set_attributes(gc_context, ClassAttributes::FINAL); object_c_class.define_instance_trait( gc_context, diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 320ccaa1a..3a1ace3b0 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -1107,12 +1107,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Get a raw pointer value for this object. fn as_ptr(&self) -> *const ObjectPtr; - /// Get this object's class, if it has one. - fn instance_of(&self) -> Option> { - let base = self.base(); - base.instance_of() - } - /// Get this object's vtable, if it has one. /// Every object with class should have a vtable fn vtable(&self) -> Option> { @@ -1127,7 +1121,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// Get this object's class's `Class`, if it has one. fn instance_class(&self) -> Option> { - self.instance_of().map(|cls| cls.inner_class_definition()) + let base = self.base(); + base.instance_class() } /// Get this object's class's name, formatted for debug output. @@ -1137,11 +1132,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy .unwrap_or_else(|| "".into()) } - fn set_instance_of(&self, mc: &Mutation<'gc>, instance_of: ClassObject<'gc>) { - let instance_vtable = instance_of.instance_vtable(); - + fn set_instance_class(&self, mc: &Mutation<'gc>, instance_class: Class<'gc>) { let mut base = self.base_mut(mc); - base.set_instance_of(instance_of, instance_vtable); + base.set_instance_class(instance_class); } // Sets a different vtable for object, without changing instance_of. diff --git a/core/src/avm2/object/bytearray_object.rs b/core/src/avm2/object/bytearray_object.rs index a5f949a67..0d4e0c766 100644 --- a/core/src/avm2/object/bytearray_object.rs +++ b/core/src/avm2/object/bytearray_object.rs @@ -19,7 +19,7 @@ pub fn byte_array_allocator<'gc>( .context .library .avm2_class_registry() - .class_symbol(class) + .class_symbol(class.inner_class_definition()) { if let Some(lib) = activation.context.library.library_for_movie(movie) { if let Some(Character::BinaryData(binary_data)) = lib.character_by_id(id) { diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 39e86ed51..5be3196bf 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -112,19 +112,18 @@ impl<'gc> ClassObject<'gc> { class: Class<'gc>, superclass_object: Option>, ) -> Result> { + let c_class = class + .c_class() + .expect("Can only call ClassObject::from_class on i_classes"); + let class_object = Self::from_class_partial(activation, class, superclass_object)?; let class_proto = class_object.allocate_prototype(activation, superclass_object)?; class_object.link_prototype(activation, class_proto)?; - let class_class = activation.avm2().classes().class; - let class_class_proto = class_class.prototype(); + let class_class_proto = activation.avm2().classes().class.prototype(); - class_object.link_type( - activation.context.gc_context, - class_class_proto, - class_class, - ); + class_object.link_type(activation.context.gc_context, class_class_proto, c_class); class_object.init_instance_vtable(activation)?; class_object.into_finished_class(activation) } @@ -146,10 +145,6 @@ impl<'gc> ClassObject<'gc> { class: Class<'gc>, superclass_object: Option>, ) -> Result> { - // We should only be able to construct `i_class` Classes - // (if i_class() is None it means that the Class is an i_class) - assert!(class.i_class().is_none()); - let scope = activation.create_scopechain(); if let Some(base_class) = superclass_object.map(|b| b.inner_class_definition()) { if base_class.is_final() { @@ -324,17 +319,10 @@ impl<'gc> ClassObject<'gc> { /// This is intended to support initialization of early types such as /// `Class` and `Object`. All other types should pull `Class`'s prototype /// and type object from the `Avm2` instance. - pub fn link_type( - self, - gc_context: &Mutation<'gc>, - proto: Object<'gc>, - instance_of: ClassObject<'gc>, - ) { - let instance_vtable = instance_of.instance_vtable(); - + pub fn link_type(self, gc_context: &Mutation<'gc>, proto: Object<'gc>, c_class: Class<'gc>) { let mut write = self.0.write(gc_context); - write.base.set_instance_of(instance_of, instance_vtable); + write.base.set_instance_class(c_class); write.base.set_proto(proto); } diff --git a/core/src/avm2/object/font_object.rs b/core/src/avm2/object/font_object.rs index c672b0bfa..8d876d5f5 100644 --- a/core/src/avm2/object/font_object.rs +++ b/core/src/avm2/object/font_object.rs @@ -20,7 +20,7 @@ pub fn font_allocator<'gc>( .context .library .avm2_class_registry() - .class_symbol(class) + .class_symbol(class.inner_class_definition()) { if let Some(lib) = activation.context.library.library_for_movie(movie) { if let Some(Character::Font(font)) = lib.character_by_id(id) { diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 8c984d1cd..01d7b99a2 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -53,9 +53,9 @@ pub struct ScriptObjectData<'gc> { /// Implicit prototype of this script object. proto: Option>, - /// The class object that this is an instance of. + /// The `Class` that this is an instance of. /// If `none`, this is not an ES4 object at all. - instance_of: Option>, + instance_class: Option>, /// The table used for non-dynamic property lookups. vtable: Option>, @@ -143,7 +143,7 @@ impl<'gc> ScriptObjectData<'gc> { slots: Vec::new(), bound_methods: Vec::new(), proto, - instance_of, + instance_class: instance_of.map(|cls| cls.inner_class_definition()), vtable: instance_of.map(|cls| cls.instance_vtable()), } } @@ -390,13 +390,9 @@ impl<'gc> ScriptObjectData<'gc> { *self.bound_methods.get_mut(disp_id as usize).unwrap() = Some(function); } - /// Get the class object for this object, if it has one. - pub fn instance_of(&self) -> Option> { - self.instance_of - } - + /// Get the `Class` for this object, if it has one. pub fn instance_class(&self) -> Option> { - self.instance_of.map(|cls| cls.inner_class_definition()) + self.instance_class } /// Get the vtable for this object, if it has one. @@ -411,9 +407,8 @@ impl<'gc> ScriptObjectData<'gc> { } /// Set the class object for this object. - pub fn set_instance_of(&mut self, instance_of: ClassObject<'gc>, vtable: VTable<'gc>) { - self.instance_of = Some(instance_of); - self.vtable = Some(vtable); + pub fn set_instance_class(&mut self, instance_class: Class<'gc>) { + self.instance_class = Some(instance_class); } pub fn set_vtable(&mut self, vtable: VTable<'gc>) { diff --git a/core/src/avm2/optimize.rs b/core/src/avm2/optimize.rs index 0af99652b..0d690b1e0 100644 --- a/core/src/avm2/optimize.rs +++ b/core/src/avm2/optimize.rs @@ -280,33 +280,30 @@ pub fn optimize<'gc>( // but this works since it's guaranteed to be set in `Activation::from_method`. let this_value = activation.local_register(0); - let (this_class, this_vtable) = if let Some(this_class) = activation.subclass_object() { + let this_class = if let Some(this_class) = activation.subclass_object() { if this_value.is_of_type(activation, this_class.inner_class_definition()) { - ( - Some(this_class), - Some(this_class.inner_class_definition().instance_vtable()), - ) - } else if this_value - .as_object() - .and_then(|o| o.as_class_object()) - .map(|c| c.inner_class_definition() == this_class.inner_class_definition()) - .unwrap_or(false) - { - // Static method - ( - Some(activation.avm2().classes().class), - Some(this_class.class_vtable()), - ) + Some(this_class.inner_class_definition()) + } else if let Some(this_object) = this_value.as_object() { + if this_object + .as_class_object() + .map(|c| c.inner_class_definition() == this_class.inner_class_definition()) + .unwrap_or(false) + { + // Static method + this_object.instance_class() + } else { + None + } } else { - (None, None) + None } } else { - (None, None) + None }; let this_value = OptValue { - class: this_class.map(|c| c.inner_class_definition()), - vtable: this_vtable, + class: this_class, + vtable: this_class.map(|cls| cls.instance_vtable()), contains_valid_integer: false, contains_valid_unsigned: false, null_state: NullState::NotNull, @@ -861,7 +858,7 @@ pub fn optimize<'gc>( if !multiname.has_lazy_component() && has_simple_scoping { let outer_scope = activation.outer(); if !outer_scope.is_empty() { - if let Some(this_vtable) = this_vtable { + if let Some(this_vtable) = this_class.map(|cls| cls.instance_vtable()) { if this_vtable.has_trait(&multiname) { *op = Op::GetScopeObject { index: 0 }; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index 650c7a7a5..8510ecd17 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -267,7 +267,7 @@ impl<'gc> TranslationUnit<'gc> { self, script_index, global_obj, - global_classdef, + global_class, domain, activation, )?; @@ -430,6 +430,9 @@ pub struct ScriptData<'gc> { /// The class of this script's global object. global_class: Option>, + /// The ClassObject of this script's global object. + global_class_obj: Option>, + /// The domain associated with this script. domain: Domain<'gc>, @@ -468,6 +471,7 @@ impl<'gc> Script<'gc> { ScriptData { globals, global_class: None, + global_class_obj: None, domain, init: Method::from_builtin( |_, _, _| Ok(Value::Undefined), @@ -496,7 +500,7 @@ impl<'gc> Script<'gc> { unit: TranslationUnit<'gc>, script_index: u32, globals: Object<'gc>, - global_class: Class<'gc>, + global_class_obj: ClassObject<'gc>, domain: Domain<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result> { @@ -513,7 +517,8 @@ impl<'gc> Script<'gc> { activation.context.gc_context, ScriptData { globals, - global_class: Some(global_class), + global_class: Some(global_class_obj.inner_class_definition()), + global_class_obj: Some(global_class_obj), domain, init, traits: Vec::new(), @@ -608,6 +613,10 @@ impl<'gc> Script<'gc> { self.0.write(mc).global_class = Some(global_class); } + pub fn set_global_class_obj(self, mc: &Mutation<'gc>, global_class_obj: ClassObject<'gc>) { + self.0.write(mc).global_class_obj = Some(global_class_obj); + } + /// Return the global scope for the script. /// /// If the script has not yet been initialized, this will initialize it on @@ -628,7 +637,7 @@ impl<'gc> Script<'gc> { globals.vtable().unwrap().init_vtable( globals.instance_class().unwrap(), - globals.instance_of(), + self.0.read().global_class_obj, &self.traits()?, Some(scope), None, diff --git a/core/src/debug_ui/avm2.rs b/core/src/debug_ui/avm2.rs index 45a5a429c..b5ab84f4f 100644 --- a/core/src/debug_ui/avm2.rs +++ b/core/src/debug_ui/avm2.rs @@ -87,7 +87,7 @@ impl Avm2ObjectWindow { .num_columns(2) .striped(true) .show(ui, |ui| { - if let Some(class) = object.instance_of() { + if let Some(class) = object.instance_class().and_then(|o| o.class_object()) { ui.label("Instance Of"); show_avm2_value(ui, &mut activation.context, class.into(), messages); ui.end_row(); diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 77581e184..947382d04 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -4548,7 +4548,11 @@ impl<'gc, 'a> MovieClip<'gc> { .context .library .avm2_class_registry_mut() - .set_class_symbol(class_object, movie.clone(), id); + .set_class_symbol( + class_object.inner_class_definition(), + movie.clone(), + id, + ); let library = activation .context diff --git a/core/src/library.rs b/core/src/library.rs index 5b5a5e7a8..6d9fe18cc 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -1,5 +1,5 @@ use crate::avm1::{PropertyMap as Avm1PropertyMap, PropertyMap}; -use crate::avm2::{ClassObject as Avm2ClassObject, Domain as Avm2Domain}; +use crate::avm2::{Class as Avm2Class, Domain as Avm2Domain}; use crate::backend::audio::SoundHandle; use crate::character::Character; use std::borrow::Cow; @@ -48,7 +48,7 @@ impl WeakElement for WeakMovieSymbol { pub struct Avm2ClassRegistry<'gc> { /// A list of AVM2 class objects and the character IDs they are expected to /// instantiate. - class_map: WeakValueHashMap, WeakMovieSymbol>, + class_map: WeakValueHashMap, WeakMovieSymbol>, } unsafe impl Collect for Avm2ClassRegistry<'_> { @@ -76,24 +76,21 @@ impl<'gc> Avm2ClassRegistry<'gc> { /// /// A value of `None` indicates that this AVM2 class is not associated with /// a library symbol. - pub fn class_symbol( - &self, - class_object: Avm2ClassObject<'gc>, - ) -> Option<(Arc, CharacterId)> { - match self.class_map.get(&class_object) { + pub fn class_symbol(&self, class_def: Avm2Class<'gc>) -> Option<(Arc, CharacterId)> { + match self.class_map.get(&class_def) { Some(MovieSymbol(movie, symbol)) => Some((movie, symbol)), None => None, } } - /// Associate an AVM2 class object with a given library symbol. + /// Associate an AVM2 class definition with a given library symbol. pub fn set_class_symbol( &mut self, - class_object: Avm2ClassObject<'gc>, + class_def: Avm2Class<'gc>, movie: Arc, symbol: CharacterId, ) { - if let Some(old) = self.class_map.get(&class_object) { + if let Some(old) = self.class_map.get(&class_def) { if Arc::ptr_eq(&movie, &old.0) && symbol != old.1 { // Flash player actually allows using the same class in multiple SymbolClass // entries in the same swf, with *different* symbol ids. Whichever one @@ -102,7 +99,7 @@ impl<'gc> Avm2ClassRegistry<'gc> { // of deliberately crafted SWFs. tracing::warn!( "Tried to overwrite class {:?} id={:?} with symbol id={:?} from same movie", - class_object, + class_def, old.1, symbol, ); @@ -114,8 +111,7 @@ impl<'gc> Avm2ClassRegistry<'gc> { // instantiates the clip on the timeline. return; } - self.class_map - .insert(class_object, MovieSymbol(movie, symbol)); + self.class_map.insert(class_def, MovieSymbol(movie, symbol)); } }