From 6cc1488f73fc3ad2698e12a9d625ba3cbb3a12ae Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Fri, 21 Jun 2024 10:03:59 -0700 Subject: [PATCH] avm2: Remove all classless objects --- core/src/avm2.rs | 4 +-- core/src/avm2/activation.rs | 4 +-- core/src/avm2/globals.rs | 26 ++++++++------ .../globals/flash/events/event_dispatcher.rs | 2 +- core/src/avm2/object/class_object.rs | 4 +-- core/src/avm2/object/dispatch_object.rs | 7 ++-- core/src/avm2/object/function_object.rs | 13 ++++--- core/src/avm2/object/script_object.rs | 36 ++++++++++++++----- core/src/display_object/stage.rs | 22 +++++++----- 9 files changed, 70 insertions(+), 48 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index a7a9c7224..f9481edec 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -81,8 +81,8 @@ pub use crate::avm2::globals::flash::ui::context_menu::make_context_menu_state; pub use crate::avm2::multiname::Multiname; pub use crate::avm2::namespace::Namespace; pub use crate::avm2::object::{ - ArrayObject, BitmapDataObject, ClassObject, EventObject, Object, ScriptObject, - SoundChannelObject, StageObject, TObject, + ArrayObject, BitmapDataObject, ClassObject, EventObject, Object, SoundChannelObject, + StageObject, TObject, }; pub use crate::avm2::qname::QName; pub use crate::avm2::value::Value; diff --git a/core/src/avm2/activation.rs b/core/src/avm2/activation.rs index 37387bdd4..349e9d6e1 100644 --- a/core/src/avm2/activation.rs +++ b/core/src/avm2/activation.rs @@ -1589,9 +1589,9 @@ impl<'a, 'gc> Activation<'a, 'gc> { let vname = ex.variable_name; let so = if let Some(vname) = vname { - ScriptObject::catch_scope(self.context.gc_context, &vname) + ScriptObject::catch_scope(self, &vname) } else { - // for `finally` scopes, FP just creates a bare object. + // for `finally` scopes, FP just creates a normal object. self.avm2().classes().object.construct(self, &[])? }; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 66adc34d0..9a0f02f60 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -472,12 +472,7 @@ pub fn load_player_globals<'gc>( ) -> Result<(), Error<'gc>> { let mc = activation.context.gc_context; - let globals = ScriptObject::custom_object(mc, None, None); - let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]); - let script = Script::empty_script(mc, globals, domain); - // Set the outer scope of this activation to the global scope. - activation.set_outer(gs); // public / root package // @@ -510,22 +505,31 @@ pub fn load_player_globals<'gc>( class_i_class.set_c_class(mc, class_c_class); class_c_class.set_i_class(mc, class_i_class); - domain.export_class(object_i_class.name(), object_i_class, mc); - domain.export_class(class_i_class.name(), class_i_class, mc); - // Function is more of a "normal" class than the other two, so we can create it normally. let fn_classdef = function::create_class(activation, object_i_class, class_i_class); + + // Register the classes in the domain, now + domain.export_class(object_i_class.name(), object_i_class, mc); + domain.export_class(class_i_class.name(), class_i_class, mc); domain.export_class(fn_classdef.name(), fn_classdef, mc); + // Initialize the script + let globals = ScriptObject::custom_object(mc, object_i_class, None, None); + let script = Script::empty_script(mc, globals, domain); + + let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]); + activation.set_outer(gs); + let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?; - let object_proto = ScriptObject::custom_object(mc, Some(object_class), None); + let object_proto = ScriptObject::custom_object(mc, object_i_class, Some(object_class), None); let class_class = ClassObject::from_class_partial(activation, class_i_class, Some(object_class))?; - let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto)); + let class_proto = + ScriptObject::custom_object(mc, object_i_class, Some(object_class), Some(object_proto)); let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?; - let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto)); + let fn_proto = ScriptObject::custom_object(mc, fn_classdef, Some(fn_class), Some(object_proto)); // Now to weave the Gordian knot... object_class.link_prototype(activation, object_proto)?; diff --git a/core/src/avm2/globals/flash/events/event_dispatcher.rs b/core/src/avm2/globals/flash/events/event_dispatcher.rs index 1522694e5..e0e51d3b7 100644 --- a/core/src/avm2/globals/flash/events/event_dispatcher.rs +++ b/core/src/avm2/globals/flash/events/event_dispatcher.rs @@ -19,7 +19,7 @@ fn dispatch_list<'gc>( )? { Value::Object(o) => Ok(o), _ => { - let dispatch_list = DispatchObject::empty_list(activation.context.gc_context); + let dispatch_list = DispatchObject::empty_list(activation); this.init_property( &Multiname::new(activation.avm2().flash_events_internal, "_dispatchList"), dispatch_list.into(), diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index bfe34794b..034551409 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -163,7 +163,7 @@ impl<'gc> ClassObject<'gc> { let class_object = ClassObject(GcCell::new( activation.context.gc_context, ClassObjectData { - base: ScriptObjectData::custom_new(None, None), + base: ScriptObjectData::custom_new(c_class, None, None), class, prototype: None, class_scope: scope, @@ -186,8 +186,6 @@ impl<'gc> ClassObject<'gc> { .instance_scope = instance_scope; class_object.init_instance_vtable(activation)?; - class_object.set_instance_class(activation.context.gc_context, c_class); - class.add_class_object(activation.context.gc_context, class_object); Ok(class_object) diff --git a/core/src/avm2/object/dispatch_object.rs b/core/src/avm2/object/dispatch_object.rs index 53535bab5..f3dd24da6 100644 --- a/core/src/avm2/object/dispatch_object.rs +++ b/core/src/avm2/object/dispatch_object.rs @@ -63,12 +63,11 @@ pub struct DispatchObjectData<'gc> { impl<'gc> DispatchObject<'gc> { /// Construct an empty dispatch list. - pub fn empty_list(mc: &Mutation<'gc>) -> Object<'gc> { - // TODO: we might want this to be a proper Object instance, just in case - let base = ScriptObjectData::custom_new(None, None); + pub fn empty_list(activation: &mut Activation<'_, 'gc>) -> Object<'gc> { + let base = ScriptObjectData::new(activation.avm2().classes().object); DispatchObject(GcCell::new( - mc, + activation.context.gc_context, DispatchObjectData { base, dispatch: DispatchList::new(), diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index aeed048b2..a34c2652b 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -95,13 +95,11 @@ impl<'gc> FunctionObject<'gc> { scope: ScopeChain<'gc>, ) -> Result, Error<'gc>> { let this = Self::from_method(activation, method, scope, None, None); - let es3_proto = ScriptObject::custom_object( - activation.context.gc_context, - // TODO: is this really a class-less object? - // (also: how much of "ES3 class-less object" is even true?) - None, - Some(activation.avm2().classes().object.prototype()), - ); + let es3_proto = activation + .avm2() + .classes() + .object + .construct(activation, &[])?; this.0.write(activation.context.gc_context).prototype = Some(es3_proto); @@ -204,6 +202,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> { let instance = ScriptObject::custom_object( activation.context.gc_context, + activation.avm2().classes().object.inner_class_definition(), Some(activation.avm2().classes().object), Some(prototype), ); diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 01d7b99a2..c886ab907 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -105,23 +105,33 @@ impl<'gc> ScriptObject<'gc> { /// is technically also equivalent and faster, but not recommended outside lower-level Core code) pub fn custom_object( mc: &Mutation<'gc>, - class: Option>, + class: Class<'gc>, + class_obj: Option>, proto: Option>, ) -> Object<'gc> { - ScriptObject(GcCell::new(mc, ScriptObjectData::custom_new(proto, class))).into() + ScriptObject(GcCell::new( + mc, + ScriptObjectData::custom_new(class, proto, class_obj), + )) + .into() } /// A special case for `newcatch` implementation. Basically a variable (q)name /// which maps to slot 1. - pub fn catch_scope(mc: &Mutation<'gc>, qname: &QName<'gc>) -> Object<'gc> { + pub fn catch_scope(activation: &mut Activation<'_, 'gc>, qname: &QName<'gc>) -> Object<'gc> { // TODO: use a proper ClassObject here; purposefully crafted bytecode // can observe (the lack of) it. - let mut base = ScriptObjectData::custom_new(None, None); - let vt = VTable::newcatch(mc, qname); + let mut base = ScriptObjectData::custom_new( + activation.avm2().classes().object.inner_class_definition(), + None, + None, + ); + + let vt = VTable::newcatch(activation.context.gc_context, qname); base.set_vtable(vt); base.install_instance_slots(); - ScriptObject(GcCell::new(mc, base)).into() + ScriptObject(GcCell::new(activation.context.gc_context, base)).into() } } @@ -129,7 +139,11 @@ impl<'gc> ScriptObjectData<'gc> { /// Create new object data of a given class. /// This is a low-level function used to implement things like object allocators. pub fn new(instance_of: ClassObject<'gc>) -> Self { - Self::custom_new(Some(instance_of.prototype()), Some(instance_of)) + Self::custom_new( + instance_of.inner_class_definition(), + Some(instance_of.prototype()), + Some(instance_of), + ) } /// Create new custom object data of a given possibly-none class and prototype. @@ -137,13 +151,17 @@ impl<'gc> ScriptObjectData<'gc> { /// This should *not* be used, unless you really need /// to do something weird or lazily initialize the object. /// You shouldn't let scripts observe this weirdness. - pub fn custom_new(proto: Option>, instance_of: Option>) -> Self { + pub fn custom_new( + class: Class<'gc>, + proto: Option>, + instance_of: Option>, + ) -> Self { ScriptObjectData { values: Default::default(), slots: Vec::new(), bound_methods: Vec::new(), proto, - instance_class: instance_of.map(|cls| cls.inner_class_definition()), + instance_class: Some(class), vtable: instance_of.map(|cls| cls.instance_vtable()), } } diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index 880e3c4b3..82beb9b2f 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -4,7 +4,7 @@ use crate::avm1::Object as Avm1Object; use crate::avm2::object::TObject; use crate::avm2::{ Activation as Avm2Activation, Avm2, EventObject as Avm2EventObject, Object as Avm2Object, - ScriptObject as Avm2ScriptObject, StageObject as Avm2StageObject, Value as Avm2Value, + StageObject as Avm2StageObject, Value as Avm2Value, }; use crate::backend::ui::MouseCursor; use crate::config::Letterbox; @@ -130,10 +130,10 @@ pub struct StageData<'gc> { show_menu: bool, /// The AVM2 view of this stage object. - avm2_object: Avm2Object<'gc>, + avm2_object: Option>, /// The AVM2 'LoaderInfo' object for this stage object - loader_info: Avm2Object<'gc>, + loader_info: Option>, /// An array of AVM2 'Stage3D' instances stage3ds: Vec>, @@ -183,8 +183,8 @@ impl<'gc> Stage<'gc> { window_mode: Default::default(), show_menu: true, stage_focus_rect: true, - avm2_object: Avm2ScriptObject::custom_object(gc_context, None, None), - loader_info: Avm2ScriptObject::custom_object(gc_context, None, None), + avm2_object: None, + loader_info: None, stage3ds: vec![], movie, viewport_matrix: Matrix::IDENTITY, @@ -242,7 +242,7 @@ impl<'gc> Stage<'gc> { } pub fn set_loader_info(self, gc_context: &Mutation<'gc>, loader_info: Avm2Object<'gc>) { - self.0.write(gc_context).loader_info = loader_info; + self.0.write(gc_context).loader_info = Some(loader_info); } // Get the invalidation state @@ -787,7 +787,7 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { match avm2_stage { Ok(avm2_stage) => { let mut write = self.0.write(activation.context.gc_context); - write.avm2_object = avm2_stage.into(); + write.avm2_object = Some(avm2_stage.into()); write.stage3ds = vec![stage3d]; } Err(e) => tracing::error!("Unable to construct AVM2 Stage: {}", e), @@ -864,11 +864,15 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { } fn object2(&self) -> Avm2Value<'gc> { - self.0.read().avm2_object.into() + self.0 + .read() + .avm2_object + .expect("Attempted to access Stage::object2 before initialization") + .into() } fn loader_info(&self) -> Option> { - Some(self.0.read().loader_info) + self.0.read().loader_info } fn movie(&self) -> Arc {