From bf6ccfeee1e7e201ab518ef8a6c1e5db39827e4b Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 4 Jul 2020 23:13:13 -0400 Subject: [PATCH 01/25] Add some convenience functions for defining native classes. --- core/src/avm2/class.rs | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 1c1969c7e..549a78dac 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -97,6 +97,39 @@ fn do_trait_lookup<'gc>( } impl<'gc> Class<'gc> { + /// Create a new class. + /// + /// This function is primarily intended for use by native code to define + /// builtin classes. The absolute minimum necessary to define a class is + /// required here; further methods allow further changes to the class. + /// + /// Classes created in this way cannot have traits loaded from an ABC file + /// using `load_traits`. + pub fn new( + name: QName, + instance_init: Method<'gc>, + class_init: Method<'gc>, + mc: MutationContext<'gc, '_>, + ) -> GcCell<'gc, Self> { + GcCell::allocate( + mc, + Self { + name, + super_class: None, + is_sealed: false, + is_final: false, + is_interface: false, + protected_namespace: None, + interfaces: Vec::new(), + instance_init, + instance_traits: Vec::new(), + class_init, + class_traits: Vec::new(), + traits_loaded: true, + }, + ) + } + /// Construct a class from a `TranslationUnit` and it's class index. /// /// The returned class will be allocated, but no traits will be loaded. The @@ -221,6 +254,14 @@ impl<'gc> Class<'gc> { &self.super_class } + /// Define a trait on the class. + /// + /// Class traits will be accessible as properties on the class constructor + /// function. + pub fn define_class_trait(&mut self, my_trait: Trait<'gc>) { + self.class_traits.push(my_trait); + } + /// Given a name, append class traits matching the name to a list of known /// traits. /// @@ -265,6 +306,15 @@ impl<'gc> Class<'gc> { None } + /// Define a trait on instances of the class. + /// + /// Instance traits will be accessible as properties on instances of the + /// class. They will not be accessible on the class prototype, and any + /// properties defined on the prototype will be shadowed by these traits. + pub fn define_instance_trait(&mut self, my_trait: Trait<'gc>) { + self.class_traits.push(my_trait); + } + /// Given a name, append instance traits matching the name to a list of /// known traits. /// From 44b8e5d9c7acb567a61a0ee23395a34a154988e9 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 6 Jul 2020 17:43:00 -0400 Subject: [PATCH 02/25] Wrap up the existing sealed/final/interface bits in a `ClassAttributes` enumset. --- core/src/avm2/class.rs | 53 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 549a78dac..5f6a33aff 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -6,9 +6,29 @@ use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; use crate::avm2::string::AvmString; use crate::avm2::{Avm2, Error}; +use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, GcCell, MutationContext}; use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(T); + +/// All possible attributes for a given class. +#[derive(EnumSetType, Debug)] +pub enum ClassAttributes { + /// Class is sealed, attempts to set or init dynamic properties on an + /// object will generate a runtime error. + Sealed, + + /// Class is final, attempts to construct child classes from it will + /// generate a verification error. + Final, + + /// Class is an interface. + Interface, +} + /// A loaded ABC Class which can be used to construct objects with. #[derive(Clone, Debug, Collect)] #[collect(no_drop)] @@ -19,14 +39,8 @@ pub struct Class<'gc> { /// The name of this class's superclass. super_class: Option>, - /// If this class is sealed (dynamic property writes should fail) - is_sealed: bool, - - /// If this class is final (subclassing should fail) - is_final: bool, - - /// If this class is an interface - is_interface: bool, + /// Attributes of the given class. + attributes: CollectWrapper>, /// The namespace that protected traits of this class are stored into. protected_namespace: Option>, @@ -106,7 +120,7 @@ impl<'gc> Class<'gc> { /// Classes created in this way cannot have traits loaded from an ABC file /// using `load_traits`. pub fn new( - name: QName, + name: QName<'gc>, instance_init: Method<'gc>, class_init: Method<'gc>, mc: MutationContext<'gc, '_>, @@ -116,9 +130,7 @@ impl<'gc> Class<'gc> { Self { name, super_class: None, - is_sealed: false, - is_final: false, - is_interface: false, + attributes: CollectWrapper(EnumSet::empty()), protected_namespace: None, interfaces: Vec::new(), instance_init, @@ -182,14 +194,25 @@ impl<'gc> Class<'gc> { let instance_init = unit.load_method(abc_instance.init_method.0, mc)?; let class_init = unit.load_method(abc_class.init_method.0, mc)?; + let mut attributes = EnumSet::new(); + if abc_instance.is_sealed { + attributes |= ClassAttributes::Sealed; + } + + if abc_instance.is_final { + attributes |= ClassAttributes::Final; + } + + if abc_instance.is_interface { + attributes |= ClassAttributes::Interface; + } + Ok(GcCell::allocate( mc, Self { name, super_class, - is_sealed: abc_instance.is_sealed, - is_final: abc_instance.is_final, - is_interface: abc_instance.is_interface, + attributes: CollectWrapper(attributes), protected_namespace, interfaces, instance_init, From d96596fd8ac0df1b9b937d19f05f90c2b42bbe00 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 6 Jul 2020 17:43:30 -0400 Subject: [PATCH 03/25] Add a function to manually change attributes. --- core/src/avm2/class.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 5f6a33aff..e9bc6ec20 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -142,6 +142,11 @@ impl<'gc> Class<'gc> { ) } + /// Set the attributes of the class (sealed/final/interface status). + pub fn set_attributes(&mut self, attributes: EnumSet) { + self.attributes = CollectWrapper(attributes); + } + /// Construct a class from a `TranslationUnit` and it's class index. /// /// The returned class will be allocated, but no traits will be loaded. The From 4a2a4566665646f9883cb1d4e5c582c4aedbd8c4 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 00:18:52 -0400 Subject: [PATCH 04/25] `Class::new` should also take the superclass name. --- core/src/avm2/class.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index e9bc6ec20..4ebb4b119 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -121,6 +121,7 @@ impl<'gc> Class<'gc> { /// using `load_traits`. pub fn new( name: QName<'gc>, + super_class: Option>, instance_init: Method<'gc>, class_init: Method<'gc>, mc: MutationContext<'gc, '_>, @@ -129,7 +130,7 @@ impl<'gc> Class<'gc> { mc, Self { name, - super_class: None, + super_class, attributes: CollectWrapper(EnumSet::empty()), protected_namespace: None, interfaces: Vec::new(), From f09c35c4b923c49d8cf350c0c8ca467a8868e6cc Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 00:20:57 -0400 Subject: [PATCH 05/25] `From>` doesn't always work, but using an explicit non-trait method sometimes does. I have no idea why this is necessary - I was in a context where what *should* have been a `NativeMethod<'gc>` was instead being interpreted as some different function type with all the same lifetimes, but with an extra `'gc` lifetime as well. Funneling this through a non-trait method bypasses whatever is going on with the trait solver, and then at that point the trait solver knows what to do. Consider this an extra level of conversion. --- core/src/avm2/method.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index d12d29397..70b6a64be 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -162,6 +162,12 @@ impl<'gc> From>> for Method<'gc> { } impl<'gc> Method<'gc> { + /// Builtin method constructor, because for some reason `nf.into()` just + /// causes odd lifetime mismatches. + pub fn from_builtin(nf: NativeMethod<'gc>) -> Self { + Self::Native(nf) + } + /// Access the bytecode of this method. /// /// This function returns `Err` if there is no bytecode for this method. From b0b6cec117585a46b4bddcfc50dd61fc859c2689 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 00:21:21 -0400 Subject: [PATCH 06/25] Allow expanding a `QName` into a `Multiname` that selects exactly the `QName` and no more. --- core/src/avm2/names.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 69d7a7bef..50d47f618 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -357,3 +357,12 @@ impl<'gc> Multiname<'gc> { self.name } } + +impl<'gc> From> for Multiname<'gc> { + fn from(q: QName<'gc>) -> Self { + Self { + ns: vec![q.ns], + name: Some(q.name), + } + } +} From 6f284f60eb5dacb7a25fb2c225a2578dc27731a6 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 00:21:39 -0400 Subject: [PATCH 07/25] Allow constructing a trait from a class. --- core/src/avm2/trait.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 9e3b6d91e..dbb028e01 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -81,6 +81,17 @@ pub enum TraitKind<'gc> { } impl<'gc> Trait<'gc> { + pub fn from_class(class: GcCell<'gc, Class<'gc>>) -> Self { + let name = class.read().name().clone(); + + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Class { slot_id: 0, class }, + } + } + /// Convert an ABC trait into a loaded trait. pub fn from_abc_trait( unit: TranslationUnit<'gc>, From 3585cf983b7de669d901a7a34b8d1f776b062f04 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 7 Jul 2020 00:24:16 -0400 Subject: [PATCH 08/25] Convert our stub implementations of all non-ECMA classes into `Class`es. This was surprisingly tricky - due to the need to look up superclasses, class trait instantiation requires an active `Activation` and `UpdateContext`. We can't get those during VM instance creation, since the player needs the VM first before it can give it a context to work with. Ergo, we have to tear the global scope initialization in two. At the first possible moment, the player calls a new `load_player_globals` method that initializes all class traits in global scope. --- core/src/avm2.rs | 5 + core/src/avm2/globals.rs | 165 +++++++++--------- .../globals/flash/display/displayobject.rs | 37 ++-- .../flash/display/displayobjectcontainer.rs | 40 +++-- .../flash/display/interactiveobject.rs | 37 ++-- .../avm2/globals/flash/display/movieclip.rs | 37 ++-- core/src/avm2/globals/flash/display/sprite.rs | 43 +++-- .../globals/flash/events/eventdispatcher.rs | 37 ++-- core/src/player.rs | 4 + 9 files changed, 248 insertions(+), 157 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 8dd933db8..6a490cfaf 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -77,6 +77,11 @@ impl<'gc> Avm2<'gc> { } } + pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> { + let mut activation = Activation::from_nothing(context.reborrow()); + globals::load_player_globals(&mut activation) + } + /// Return the current set of system prototypes. pub fn prototypes(&self) -> &SystemPrototypes<'gc> { &self.system_prototypes diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index eab4c7f02..4c195c6e3 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -1,14 +1,15 @@ //! Global scope built-ins use crate::avm2::activation::Activation; +use crate::avm2::class::Class; use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; -use crate::avm2::object::{FunctionObject, ScriptObject}; -use crate::avm2::object::{Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; +use crate::avm2::r#trait::Trait; use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::{Collect, MutationContext}; +use gc_arena::{Collect, GcCell, MutationContext}; use std::f64::NAN; mod boolean; @@ -67,8 +68,8 @@ fn function<'gc>( .unwrap() } -/// Add a class builtin to the global scope. -fn class<'gc>( +/// Add an ES3-style builtin to the global scope. +fn oldstyle_class<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, package: impl Into>, @@ -88,6 +89,17 @@ fn class<'gc>( .unwrap(); } +/// Add a class builtin to the global scope. +fn class<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + mut global_scope: Object<'gc>, + class_def: GcCell<'gc, Class<'gc>>, +) -> Result<(), Error> { + let class_trait = Trait::from_class(class_def); + + global_scope.install_trait(activation, class_trait, global_scope) +} + /// Add a builtin constant to the global scope. fn constant<'gc>( mc: MutationContext<'gc, '_>, @@ -103,6 +115,14 @@ fn constant<'gc>( /// /// This function returns both the global scope object, as well as all builtin /// prototypes that other parts of the VM will need to use. +/// +/// Due to a limitation of our type system and our garbage collector, the +/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet. +/// As a result, global scope initialization is split into an "oldstyle phase" +/// and a "player-globals phase". This is the former phase, where we initialize +/// as much as we can without an `UpdateContext`. Note that not all +/// `SystemPrototypes` will be necessarily valid at this point in time, and +/// using them right away will result in objects of the wrong type. pub fn construct_global_scope<'gc>( mc: MutationContext<'gc, '_>, ) -> (Object<'gc>, SystemPrototypes<'gc>) { @@ -121,7 +141,7 @@ pub fn construct_global_scope<'gc>( object::fill_proto(mc, object_proto, fn_proto); - class( + oldstyle_class( mc, gs, "", @@ -130,7 +150,7 @@ pub fn construct_global_scope<'gc>( object_proto, fn_proto, ); - class( + oldstyle_class( mc, gs, "", @@ -139,7 +159,7 @@ pub fn construct_global_scope<'gc>( fn_proto, fn_proto, ); - class( + oldstyle_class( mc, gs, "", @@ -148,7 +168,7 @@ pub fn construct_global_scope<'gc>( class_proto, fn_proto, ); - class( + oldstyle_class( mc, gs, "", @@ -157,7 +177,7 @@ pub fn construct_global_scope<'gc>( string_proto, fn_proto, ); - class( + oldstyle_class( mc, gs, "", @@ -166,7 +186,7 @@ pub fn construct_global_scope<'gc>( boolean_proto, fn_proto, ); - class( + oldstyle_class( mc, gs, "", @@ -175,9 +195,9 @@ pub fn construct_global_scope<'gc>( number_proto, fn_proto, ); - class(mc, gs, "", "int", int::constructor, int_proto, fn_proto); - class(mc, gs, "", "uint", uint::constructor, uint_proto, fn_proto); - class( + oldstyle_class(mc, gs, "", "int", int::constructor, int_proto, fn_proto); + oldstyle_class(mc, gs, "", "uint", uint::constructor, uint_proto, fn_proto); + oldstyle_class( mc, gs, "", @@ -192,77 +212,6 @@ pub fn construct_global_scope<'gc>( constant(mc, gs, "", "NaN", NAN.into()); constant(mc, gs, "", "Infinity", f64::INFINITY.into()); - // package `flash.events` - let eventdispatcher_proto = - flash::events::eventdispatcher::create_proto(mc, object_proto, fn_proto); - - class( - mc, - gs, - "flash.events", - "EventDispatcher", - flash::events::eventdispatcher::constructor, - eventdispatcher_proto, - fn_proto, - ); - - // package `flash.display` - let displayobject_proto = - flash::display::displayobject::create_proto(mc, eventdispatcher_proto, fn_proto); - let interactiveobject_proto = - flash::display::interactiveobject::create_proto(mc, displayobject_proto, fn_proto); - let displayobjectcontainer_proto = - flash::display::displayobjectcontainer::create_proto(mc, interactiveobject_proto, fn_proto); - let sprite_proto = - flash::display::sprite::create_proto(mc, displayobjectcontainer_proto, fn_proto); - let movieclip_proto = flash::display::movieclip::create_proto(mc, sprite_proto, fn_proto); - - class( - mc, - gs, - "flash.display", - "DisplayObject", - flash::display::displayobject::constructor, - displayobject_proto, - fn_proto, - ); - class( - mc, - gs, - "flash.display", - "InteractiveObject", - flash::display::interactiveobject::constructor, - interactiveobject_proto, - fn_proto, - ); - class( - mc, - gs, - "flash.display", - "DisplayObjectContainer", - flash::display::displayobjectcontainer::constructor, - sprite_proto, - fn_proto, - ); - class( - mc, - gs, - "flash.display", - "Sprite", - flash::display::sprite::constructor, - sprite_proto, - fn_proto, - ); - class( - mc, - gs, - "flash.display", - "MovieClip", - flash::display::movieclip::constructor, - movieclip_proto, - fn_proto, - ); - let system_prototypes = SystemPrototypes { object: object_proto, function: fn_proto, @@ -277,3 +226,49 @@ pub fn construct_global_scope<'gc>( (gs, system_prototypes) } + +/// Initialize all remaining builtin classes. +/// +/// Due to a limitation of our type system and our garbage collector, the +/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet. +/// As a result, global scope initialization is split into an "oldstyle phase" +/// and a "player-globals phase". This is the latter phase. +pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Result<(), Error> { + let gs = activation.avm2().globals(); + + // package `flash.events` + class( + activation, + gs, + flash::events::eventdispatcher::create_class(activation.context.gc_context), + )?; + + // package `flash.display` + class( + activation, + gs, + flash::display::displayobject::create_class(activation.context.gc_context), + )?; + class( + activation, + gs, + flash::display::interactiveobject::create_class(activation.context.gc_context), + )?; + class( + activation, + gs, + flash::display::displayobjectcontainer::create_class(activation.context.gc_context), + )?; + class( + activation, + gs, + flash::display::sprite::create_class(activation.context.gc_context), + )?; + class( + activation, + gs, + flash::display::movieclip::create_class(activation.context.gc_context), + )?; + + Ok(()) +} diff --git a/core/src/avm2/globals/flash/display/displayobject.rs b/core/src/avm2/globals/flash/display/displayobject.rs index 6ebe2dfe7..22d0bf030 100644 --- a/core/src/avm2/globals/flash/display/displayobject.rs +++ b/core/src/avm2/globals/flash/display/displayobject.rs @@ -1,13 +1,16 @@ //! `flash.display.DisplayObject` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.display.DisplayObject`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.display.DisplayObject`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,22 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `DisplayObject.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.display.DisplayObject`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `DisplayObject`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package("flash.display"), "DisplayObject"), + Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs index 7f3faa95b..aca1ffbe8 100644 --- a/core/src/avm2/globals/flash/display/displayobjectcontainer.rs +++ b/core/src/avm2/globals/flash/display/displayobjectcontainer.rs @@ -1,13 +1,16 @@ //! `flash.display.DisplayObjectContainer` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.display.DisplayObjectContainer`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.display.DisplayObjectContainer`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,25 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `DisplayObjectContainer.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.display.DisplayObjectContainer`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `DisplayObjectContainer`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new( + Namespace::package("flash.display"), + "DisplayObjectContainer", + ), + Some(QName::new(Namespace::package("flash.display"), "InteractiveObject").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/flash/display/interactiveobject.rs b/core/src/avm2/globals/flash/display/interactiveobject.rs index 393bc561f..15cc8ae94 100644 --- a/core/src/avm2/globals/flash/display/interactiveobject.rs +++ b/core/src/avm2/globals/flash/display/interactiveobject.rs @@ -1,13 +1,16 @@ //! `flash.display.InteractiveObject` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.display.InteractiveObject`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.display.InteractiveObject`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,22 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `InteractiveObject.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.display.InteractiveObject`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `InteractiveObject`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package("flash.display"), "InteractiveObject"), + Some(QName::new(Namespace::package("flash.display"), "DisplayObject").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/flash/display/movieclip.rs b/core/src/avm2/globals/flash/display/movieclip.rs index 074cc4d3b..c3e9dc588 100644 --- a/core/src/avm2/globals/flash/display/movieclip.rs +++ b/core/src/avm2/globals/flash/display/movieclip.rs @@ -1,13 +1,16 @@ //! `flash.display.MovieClip` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.display.MovieClip`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.display.MovieClip`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,22 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `MovieClip.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.display.MovieClip`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `MovieClip`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package("flash.display"), "MovieClip"), + Some(QName::new(Namespace::package("flash.display"), "Sprite").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 2a28961c6..ae7ee3a9f 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -1,13 +1,16 @@ //! `flash.display.Sprite` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.display.Sprite`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.display.Sprite`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,28 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `Sprite.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.display.Sprite`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `Sprite`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package("flash.display"), "Sprite"), + Some( + QName::new( + Namespace::package("flash.display"), + "DisplayObjectContainer", + ) + .into(), + ), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/flash/events/eventdispatcher.rs b/core/src/avm2/globals/flash/events/eventdispatcher.rs index 54948e84f..362dbbf00 100644 --- a/core/src/avm2/globals/flash/events/eventdispatcher.rs +++ b/core/src/avm2/globals/flash/events/eventdispatcher.rs @@ -1,13 +1,16 @@ //! `flash.events.EventDispatcher` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `flash.events.EventDispatcher`'s constructor. -pub fn constructor<'gc>( +/// Implements `flash.events.EventDispatcher`'s instance constructor. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,12 +18,22 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -/// Construct `EventDispatcher.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - // TODO: Use `StageObject` here. - ScriptObject::object(mc, super_proto) +/// Implements `flash.events.EventDispatcher`'s class constructor. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `EventDispatcher`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package("flash.events"), "EventDispatcher"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/player.rs b/core/src/player.rs index d2602c614..b6f5585ff 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -345,6 +345,10 @@ impl Player { AvmString::new(activation.context.gc_context, version_string).into(), EnumSet::empty(), ); + + drop(activation); + + Avm2::load_player_globals(context).unwrap(); }); self.build_matrices(); From 94d51702770408e83b0a6543b12274fb31a2e1c7 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 11 Jul 2020 17:39:45 -0400 Subject: [PATCH 09/25] When manually instantiating classes for globals, make sure that the classes get the global scope object when instantiated. --- core/src/avm2/globals.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 4c195c6e3..266fefb3d 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -6,6 +6,7 @@ use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; use crate::avm2::r#trait::Trait; +use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; @@ -92,12 +93,13 @@ fn oldstyle_class<'gc>( /// Add a class builtin to the global scope. fn class<'gc>( activation: &mut Activation<'_, 'gc, '_>, - mut global_scope: Object<'gc>, + mut global: Object<'gc>, class_def: GcCell<'gc, Class<'gc>>, ) -> Result<(), Error> { let class_trait = Trait::from_class(class_def); + let global_scope = Scope::push_scope(global.get_scope(), global, activation.context.gc_context); - global_scope.install_trait(activation, class_trait, global_scope) + global.install_foreign_trait(activation, class_trait, Some(global_scope), global) } /// Add a builtin constant to the global scope. From 5e932bcb75720d8cbc9389156cdb08ccaf025195 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 11 Jul 2020 17:39:59 -0400 Subject: [PATCH 10/25] Allow implementing interfaces in built-in classes. --- core/src/avm2/class.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 4ebb4b119..9d136a869 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -401,4 +401,8 @@ impl<'gc> Class<'gc> { pub fn interfaces(&self) -> &[Multiname<'gc>] { &self.interfaces } + + pub fn implements(&mut self, iface: Multiname<'gc>) { + self.interfaces.push(iface) + } } From 9dc6cbe1ce3da9ca6d8e678b42511f24b3caf187 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Sat, 11 Jul 2020 17:57:46 -0400 Subject: [PATCH 11/25] Allow creating builtin traits from methods. --- core/src/avm2/trait.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index dbb028e01..01e43a0f4 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -92,6 +92,15 @@ impl<'gc> Trait<'gc> { } } + pub fn from_method(name: QName<'gc>, method: Method<'gc>) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Method { disp_id: 0, method }, + } + } + /// Convert an ABC trait into a loaded trait. pub fn from_abc_trait( unit: TranslationUnit<'gc>, From d366ceab0e79dc2310a8e366841c14d1510e02f2 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:11:08 -0400 Subject: [PATCH 12/25] Allow setting the protected namespace of a builtin class. --- core/src/avm2/class.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 9d136a869..dacffa6e5 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -148,6 +148,11 @@ impl<'gc> Class<'gc> { self.attributes = CollectWrapper(attributes); } + /// Add a protected namespace to this class. + pub fn set_protected_namespace(&mut self, ns: Namespace<'gc>) { + self.protected_namespace = Some(ns) + } + /// Construct a class from a `TranslationUnit` and it's class index. /// /// The returned class will be allocated, but no traits will be loaded. The From 3ca8dfd21aea14a57cc75f959af2febcad79dd60 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:14:33 -0400 Subject: [PATCH 13/25] Allow constructing traits for getters, setters, and unbound free functions. --- core/src/avm2/trait.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 01e43a0f4..eeb19f86b 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -101,6 +101,36 @@ impl<'gc> Trait<'gc> { } } + pub fn from_getter(name: QName<'gc>, method: Method<'gc>) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Getter { disp_id: 0, method }, + } + } + + pub fn from_setter(name: QName<'gc>, method: Method<'gc>) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Setter { disp_id: 0, method }, + } + } + + pub fn from_function(name: QName<'gc>, function: Method<'gc>) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Function { + slot_id: 0, + function, + }, + } + } + /// Convert an ABC trait into a loaded trait. pub fn from_abc_trait( unit: TranslationUnit<'gc>, From 60d42fa5580ab07d635432f7463bb4c411cd9452 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:20:00 -0400 Subject: [PATCH 14/25] Allow creating slot/const traits. --- core/src/avm2/trait.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index eeb19f86b..3f6c3c81b 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -131,6 +131,40 @@ impl<'gc> Trait<'gc> { } } + pub fn from_slot( + name: QName<'gc>, + type_name: Multiname<'gc>, + default_value: Option>, + ) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Slot { + slot_id: 0, + type_name, + default_value, + }, + } + } + + pub fn from_const( + name: QName<'gc>, + type_name: Multiname<'gc>, + default_value: Option>, + ) -> Self { + Trait { + name, + is_final: false, + is_override: false, + kind: TraitKind::Slot { + slot_id: 0, + type_name, + default_value, + }, + } + } + /// Convert an ABC trait into a loaded trait. pub fn from_abc_trait( unit: TranslationUnit<'gc>, From a5b62e833e9b0a1891dd2b714dbd6fb2cdcc24ab Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:39:28 -0400 Subject: [PATCH 15/25] Add trait attributes, similar to that of class attributes --- core/src/avm2/trait.rs | 81 +++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 3f6c3c81b..73782dc99 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -6,9 +6,26 @@ use crate::avm2::names::{Multiname, QName}; use crate::avm2::script::TranslationUnit; use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::{Avm2, Error}; +use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, GcCell, MutationContext}; use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind}; +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(T); + +/// All attributes a trait can have. +#[derive(Debug, EnumSetType)] +pub enum TraitAttributes { + /// Whether or not traits in downstream classes are allowed to override + /// this trait. + Final, + + /// Whether or not this trait is intended to override an upstream class's + /// trait. + Override, +} + /// Represents a trait as loaded into the VM. /// /// A trait is an uninstantiated AVM2 property. Traits are used by objects to @@ -25,18 +42,22 @@ pub struct Trait<'gc> { /// The name of this trait. name: QName<'gc>, - /// Whether or not traits in downstream classes are allowed to override - /// this trait. - is_final: bool, - - /// Whether or not this trait is intended to override an upstream class's - /// trait. - is_override: bool, + /// The attributes set on this trait. + attributes: CollectWrapper>, /// The kind of trait in use. kind: TraitKind<'gc>, } +fn trait_attribs_from_abc_traits(abc_trait: &AbcTrait) -> CollectWrapper> { + CollectWrapper(match (abc_trait.is_final, abc_trait.is_override) { + (true, true) => TraitAttributes::Final | TraitAttributes::Override, + (true, false) => TraitAttributes::Final.into(), + (false, true) => TraitAttributes::Override.into(), + (false, false) => EnumSet::empty(), + }) +} + /// The fields for a particular kind of trait. /// /// The kind of a trait also determines how it's instantiated on the object. @@ -86,8 +107,7 @@ impl<'gc> Trait<'gc> { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Class { slot_id: 0, class }, } } @@ -95,8 +115,7 @@ impl<'gc> Trait<'gc> { pub fn from_method(name: QName<'gc>, method: Method<'gc>) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Method { disp_id: 0, method }, } } @@ -104,8 +123,7 @@ impl<'gc> Trait<'gc> { pub fn from_getter(name: QName<'gc>, method: Method<'gc>) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Getter { disp_id: 0, method }, } } @@ -113,8 +131,7 @@ impl<'gc> Trait<'gc> { pub fn from_setter(name: QName<'gc>, method: Method<'gc>) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Setter { disp_id: 0, method }, } } @@ -122,8 +139,7 @@ impl<'gc> Trait<'gc> { pub fn from_function(name: QName<'gc>, function: Method<'gc>) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Function { slot_id: 0, function, @@ -138,8 +154,7 @@ impl<'gc> Trait<'gc> { ) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Slot { slot_id: 0, type_name, @@ -155,8 +170,7 @@ impl<'gc> Trait<'gc> { ) -> Self { Trait { name, - is_final: false, - is_override: false, + attributes: CollectWrapper(EnumSet::empty()), kind: TraitKind::Slot { slot_id: 0, type_name, @@ -181,8 +195,7 @@ impl<'gc> Trait<'gc> { value, } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Slot { slot_id: *slot_id, type_name: if type_name.0 == 0 { @@ -199,8 +212,7 @@ impl<'gc> Trait<'gc> { }, AbcTraitKind::Method { disp_id, method } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Method { disp_id: *disp_id, method: unit.load_method(method.0, mc)?, @@ -208,8 +220,7 @@ impl<'gc> Trait<'gc> { }, AbcTraitKind::Getter { disp_id, method } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Getter { disp_id: *disp_id, method: unit.load_method(method.0, mc)?, @@ -217,8 +228,7 @@ impl<'gc> Trait<'gc> { }, AbcTraitKind::Setter { disp_id, method } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Setter { disp_id: *disp_id, method: unit.load_method(method.0, mc)?, @@ -226,8 +236,7 @@ impl<'gc> Trait<'gc> { }, AbcTraitKind::Class { slot_id, class } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Class { slot_id: *slot_id, class: unit.load_class(class.0, avm2, mc)?, @@ -235,8 +244,7 @@ impl<'gc> Trait<'gc> { }, AbcTraitKind::Function { slot_id, function } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Function { slot_id: *slot_id, function: unit.load_method(function.0, mc)?, @@ -248,8 +256,7 @@ impl<'gc> Trait<'gc> { value, } => Trait { name, - is_final: abc_trait.is_final, - is_override: abc_trait.is_override, + attributes: trait_attribs_from_abc_traits(abc_trait), kind: TraitKind::Const { slot_id: *slot_id, type_name: if type_name.0 == 0 { @@ -276,10 +283,10 @@ impl<'gc> Trait<'gc> { } pub fn is_final(&self) -> bool { - self.is_final + self.attributes.0.contains(TraitAttributes::Final) } pub fn is_override(&self) -> bool { - self.is_override + self.attributes.0.contains(TraitAttributes::Override) } } From 4ed5050f56bb55753d3f837a27d2988e71a3c47f Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:42:03 -0400 Subject: [PATCH 16/25] Add function to change trait attributes. --- core/src/avm2/trait.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 73782dc99..59c544ac8 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -289,4 +289,8 @@ impl<'gc> Trait<'gc> { pub fn is_override(&self) -> bool { self.attributes.0.contains(TraitAttributes::Override) } + + pub fn set_attributes(&mut self, attribs: EnumSet) { + self.attributes.0 = attribs; + } } From df95482eb11b8be9ed92c51e255022a49fe8b5bd Mon Sep 17 00:00:00 2001 From: David Wendt Date: Mon, 20 Jul 2020 19:50:17 -0400 Subject: [PATCH 17/25] Allow setting `slot_id` or `disp_id` (depending on kind). --- core/src/avm2/trait.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index 59c544ac8..b7711df23 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -293,4 +293,17 @@ impl<'gc> Trait<'gc> { pub fn set_attributes(&mut self, attribs: EnumSet) { self.attributes.0 = attribs; } + + /// Set the slot or dispatch ID of this trait. + pub fn set_slot_id(&mut self, id: u32) { + match &mut self.kind { + TraitKind::Slot { slot_id, .. } => *slot_id = id, + TraitKind::Method { disp_id, .. } => *disp_id = id, + TraitKind::Getter { disp_id, .. } => *disp_id = id, + TraitKind::Setter { disp_id, .. } => *disp_id = id, + TraitKind::Class { slot_id, .. } => *slot_id = id, + TraitKind::Function { slot_id, .. } => *slot_id = id, + TraitKind::Const { slot_id, .. } => *slot_id = id, + } + } } From 22ec96b85e87497c7bfd71ccc7ce8ead81267c57 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 22:49:38 -0400 Subject: [PATCH 18/25] `install_trait` and `install_foreign_trait` should return the value of the thing they installed. --- core/src/avm2/object.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index e27d1e148..02575ab54 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -415,23 +415,29 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy /// bound to. It should always be `self` except when doing things with /// `super`, which needs to create bound methods pointing to a different /// object. + /// + /// The value of the trait at the time of installation will be returned + /// here. fn install_trait( &mut self, activation: &mut Activation<'_, 'gc, '_>, trait_entry: Trait<'gc>, reciever: Object<'gc>, - ) -> Result<(), Error> { + ) -> Result, Error> { self.install_foreign_trait(activation, trait_entry, self.get_scope(), reciever) } /// Install a trait from anywyere. + /// + /// The value of the trait at the time of installation will be returned + /// here. fn install_foreign_trait( &mut self, activation: &mut Activation<'_, 'gc, '_>, trait_entry: Trait<'gc>, scope: Option>>, reciever: Object<'gc>, - ) -> Result<(), Error> { + ) -> Result, Error> { let fn_proto = activation.avm2().prototypes().function; let trait_name = trait_entry.name().clone(); avm_debug!( @@ -447,12 +453,15 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy default_value, .. } => { + let value = default_value.clone().unwrap_or(Value::Undefined); self.install_slot( activation.context.gc_context, trait_name, *slot_id, - default_value.clone().unwrap_or(Value::Undefined), + value.clone(), ); + + Ok(value) } TraitKind::Method { disp_id, method, .. @@ -470,6 +479,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy *disp_id, function, ); + + Ok(function.into()) } TraitKind::Getter { disp_id, method, .. @@ -487,6 +498,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy *disp_id, function, )?; + + Ok(function.into()) } TraitKind::Setter { disp_id, method, .. @@ -504,6 +517,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy *disp_id, function, )?; + + Ok(function.into()) } TraitKind::Class { slot_id, class } => { let class_read = class.read(); @@ -533,6 +548,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy *slot_id, class_object.into(), ); + + Ok(class_object.into()) } TraitKind::Function { slot_id, function, .. @@ -561,22 +578,25 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy *slot_id, fobject.into(), ); + + Ok(fobject.into()) } TraitKind::Const { slot_id, default_value, .. } => { + let value = default_value.clone().unwrap_or(Value::Undefined); self.install_const( activation.context.gc_context, trait_name, *slot_id, - default_value.clone().unwrap_or(Value::Undefined), + value.clone(), ); + + Ok(value) } } - - Ok(()) } /// Call the object. From b1bcceaa78e9a0fe58dde0bbd4fb86d3079f7d49 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 22:50:46 -0400 Subject: [PATCH 19/25] Allow accessing the `Class` off of a constructor or prototype that references it. --- core/src/avm2/globals.rs | 4 +- core/src/avm2/object.rs | 3 + core/src/avm2/object/custom_object.rs | 4 + core/src/avm2/object/primitive_object.rs | 263 +---------------------- core/src/avm2/object/script_object.rs | 13 ++ 5 files changed, 29 insertions(+), 258 deletions(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 266fefb3d..85ec0d9c8 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -99,7 +99,9 @@ fn class<'gc>( let class_trait = Trait::from_class(class_def); let global_scope = Scope::push_scope(global.get_scope(), global, activation.context.gc_context); - global.install_foreign_trait(activation, class_trait, Some(global_scope), global) + global.install_foreign_trait(activation, class_trait, Some(global_scope), global)?; + + Ok(()) } /// Add a builtin constant to the global scope. diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index 02575ab54..a116e05e0 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -723,6 +723,9 @@ 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 as_class(&self) -> Option>>; + /// Get this object's `Executable`, if it has one. fn as_executable(&self) -> Option> { None diff --git a/core/src/avm2/object/custom_object.rs b/core/src/avm2/object/custom_object.rs index 7ce9f3546..746e7d3a8 100644 --- a/core/src/avm2/object/custom_object.rs +++ b/core/src/avm2/object/custom_object.rs @@ -179,6 +179,10 @@ macro_rules! impl_avm2_custom_object { self.0.as_ptr() as *const ObjectPtr } + fn as_class(&self) -> Option>> { + self.0.read().base.as_class() + } + fn install_method( &mut self, mc: MutationContext<'gc, '_>, diff --git a/core/src/avm2/object/primitive_object.rs b/core/src/avm2/object/primitive_object.rs index fd93f6b1a..c0c433782 100644 --- a/core/src/avm2/object/primitive_object.rs +++ b/core/src/avm2/object/primitive_object.rs @@ -2,7 +2,6 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; -use crate::avm2::function::Executable; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::object::{Object, ObjectPtr, TObject}; @@ -11,6 +10,7 @@ use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::impl_avm2_custom_object; use gc_arena::{Collect, GcCell, MutationContext}; /// An Object which represents a primitive value of some other kind. @@ -50,181 +50,14 @@ impl<'gc> PrimitiveObject<'gc> { } impl<'gc> TObject<'gc> for PrimitiveObject<'gc> { - fn get_property_local( - self, - reciever: Object<'gc>, - name: &QName<'gc>, - activation: &mut Activation<'_, 'gc, '_>, - ) -> Result, Error> { - let read = self.0.read(); - let rv = read.base.get_property_local(reciever, name, activation)?; + impl_avm2_custom_object!(base); - drop(read); - - rv.resolve(activation) + fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { + Ok(self.0.read().primitive.clone()) } - fn set_property_local( - self, - reciever: Object<'gc>, - name: &QName<'gc>, - value: Value<'gc>, - activation: &mut Activation<'_, 'gc, '_>, - ) -> Result<(), Error> { - let mut write = self.0.write(activation.context.gc_context); - let rv = write - .base - .set_property_local(reciever, name, value, activation)?; - - drop(write); - - rv.resolve(activation)?; - - Ok(()) - } - - fn init_property_local( - self, - reciever: Object<'gc>, - name: &QName<'gc>, - value: Value<'gc>, - activation: &mut Activation<'_, 'gc, '_>, - ) -> Result<(), Error> { - let mut write = self.0.write(activation.context.gc_context); - let rv = write - .base - .init_property_local(reciever, name, value, activation)?; - - drop(write); - - rv.resolve(activation)?; - - Ok(()) - } - - fn is_property_overwritable( - self, - gc_context: MutationContext<'gc, '_>, - name: &QName<'gc>, - ) -> bool { - self.0.write(gc_context).base.is_property_overwritable(name) - } - - fn delete_property( - &self, - gc_context: MutationContext<'gc, '_>, - multiname: &QName<'gc>, - ) -> bool { - self.0.write(gc_context).base.delete_property(multiname) - } - - fn get_slot(self, id: u32) -> Result, Error> { - self.0.read().base.get_slot(id) - } - - fn set_slot( - self, - id: u32, - value: Value<'gc>, - mc: MutationContext<'gc, '_>, - ) -> Result<(), Error> { - self.0.write(mc).base.set_slot(id, value, mc) - } - - fn init_slot( - self, - id: u32, - value: Value<'gc>, - mc: MutationContext<'gc, '_>, - ) -> Result<(), Error> { - self.0.write(mc).base.init_slot(id, value, mc) - } - - fn get_method(self, id: u32) -> Option> { - self.0.read().base.get_method(id) - } - - fn get_trait(self, name: &QName<'gc>) -> Result>, Error> { - self.0.read().base.get_trait(name) - } - - fn get_provided_trait( - &self, - name: &QName<'gc>, - known_traits: &mut Vec>, - ) -> Result<(), Error> { - self.0.read().base.get_provided_trait(name, known_traits) - } - - fn get_scope(self) -> Option>> { - self.0.read().base.get_scope() - } - - fn resolve_any(self, local_name: AvmString<'gc>) -> Result>, Error> { - self.0.read().base.resolve_any(local_name) - } - - fn resolve_any_trait( - self, - local_name: AvmString<'gc>, - ) -> Result>, Error> { - self.0.read().base.resolve_any_trait(local_name) - } - - fn has_own_property(self, name: &QName<'gc>) -> Result { - self.0.read().base.has_own_property(name) - } - - fn has_trait(self, name: &QName<'gc>) -> Result { - self.0.read().base.has_trait(name) - } - - fn provides_trait(self, name: &QName<'gc>) -> Result { - self.0.read().base.provides_trait(name) - } - - fn has_instantiated_property(self, name: &QName<'gc>) -> bool { - self.0.read().base.has_instantiated_property(name) - } - - fn has_own_virtual_getter(self, name: &QName<'gc>) -> bool { - self.0.read().base.has_own_virtual_getter(name) - } - - fn has_own_virtual_setter(self, name: &QName<'gc>) -> bool { - self.0.read().base.has_own_virtual_setter(name) - } - - fn proto(&self) -> Option> { - self.0.read().base.proto() - } - - fn get_enumerant_name(&self, index: u32) -> Option> { - self.0.read().base.get_enumerant_name(index) - } - - fn property_is_enumerable(&self, name: &QName<'gc>) -> bool { - self.0.read().base.property_is_enumerable(name) - } - - fn set_local_property_is_enumerable( - &self, - mc: MutationContext<'gc, '_>, - name: &QName<'gc>, - is_enumerable: bool, - ) -> Result<(), Error> { - self.0 - .write(mc) - .base - .set_local_property_is_enumerable(name, is_enumerable) - } - - fn as_ptr(&self) -> *const ObjectPtr { - self.0.as_ptr() as *const ObjectPtr - } - - fn as_executable(&self) -> Option> { - None + fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { + Ok(self.0.read().primitive.clone()) } fn construct( @@ -266,88 +99,4 @@ impl<'gc> TObject<'gc> for PrimitiveObject<'gc> { )) .into()) } - - fn to_string(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { - Ok(self.0.read().primitive.clone()) - } - - fn value_of(&self, _mc: MutationContext<'gc, '_>) -> Result, Error> { - Ok(self.0.read().primitive.clone()) - } - - fn install_method( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - disp_id: u32, - function: Object<'gc>, - ) { - self.0 - .write(mc) - .base - .install_method(name, disp_id, function) - } - - fn install_getter( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - disp_id: u32, - function: Object<'gc>, - ) -> Result<(), Error> { - self.0 - .write(mc) - .base - .install_getter(name, disp_id, function) - } - - fn install_setter( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - disp_id: u32, - function: Object<'gc>, - ) -> Result<(), Error> { - self.0 - .write(mc) - .base - .install_setter(name, disp_id, function) - } - - fn install_dynamic_property( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - value: Value<'gc>, - ) -> Result<(), Error> { - self.0.write(mc).base.install_dynamic_property(name, value) - } - - fn install_slot( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - id: u32, - value: Value<'gc>, - ) { - self.0.write(mc).base.install_slot(name, id, value) - } - - fn install_const( - &mut self, - mc: MutationContext<'gc, '_>, - name: QName<'gc>, - id: u32, - value: Value<'gc>, - ) { - self.0.write(mc).base.install_const(name, id, value) - } - - fn interfaces(&self) -> Vec> { - self.0.read().base.interfaces() - } - - fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>) { - self.0.write(context).base.set_interfaces(iface_list) - } } diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 7e6b367a9..c799bf559 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -337,6 +337,10 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> { fn set_interfaces(&self, context: MutationContext<'gc, '_>, iface_list: Vec>) { self.0.write(context).set_interfaces(iface_list) } + + fn as_class(&self) -> Option>> { + self.0.read().as_class() + } } impl<'gc> ScriptObject<'gc> { @@ -897,4 +901,13 @@ impl<'gc> ScriptObjectData<'gc> { pub fn set_interfaces(&mut self, iface_list: Vec>) { self.interfaces = iface_list; } + + /// Get the class for this object, if it has one. + pub fn as_class(&self) -> Option>> { + match self.class { + ScriptObjectClass::ClassConstructor(class, _) => Some(class), + ScriptObjectClass::InstancePrototype(class, _) => Some(class), + ScriptObjectClass::NoClass => None, + } + } } From a8e267ed1699832e14bd1c97d49dd7f6089c3b85 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 22:54:50 -0400 Subject: [PATCH 20/25] Allow system prototypes to be initialized after the fact. --- core/src/avm2.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 6a490cfaf..e694ecd9c 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -6,6 +6,7 @@ use crate::avm2::object::{Object, TObject}; use crate::avm2::scope::Scope; use crate::avm2::script::Script; use crate::avm2::script::TranslationUnit; +use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; @@ -56,7 +57,7 @@ pub struct Avm2<'gc> { globals: Object<'gc>, /// System prototypes. - system_prototypes: SystemPrototypes<'gc>, + system_prototypes: Option>, #[cfg(feature = "avm_debug")] pub debug_output: bool, @@ -65,12 +66,12 @@ pub struct Avm2<'gc> { impl<'gc> Avm2<'gc> { /// Construct a new AVM interpreter. pub fn new(mc: MutationContext<'gc, '_>) -> Self { - let (globals, system_prototypes) = globals::construct_global_scope(mc); + let globals = ScriptObject::bare_object(mc); Self { stack: Vec::new(), globals, - system_prototypes, + system_prototypes: None, #[cfg(feature = "avm_debug")] debug_output: false, @@ -83,8 +84,10 @@ impl<'gc> Avm2<'gc> { } /// Return the current set of system prototypes. + /// + /// This function panics if the interpreter has not yet been initialized. pub fn prototypes(&self) -> &SystemPrototypes<'gc> { - &self.system_prototypes + self.system_prototypes.as_ref().unwrap() } /// Run a script's initializer method. From 16d8c85d209cd94856d8017fb4d74b5b18d041ee Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 22:57:23 -0400 Subject: [PATCH 21/25] Load player globals as soon as possible. We're about to massively change the initialization process, and we really don't want to create another situation where the player can get caught with it's pants down. --- core/src/player.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/player.rs b/core/src/player.rs index b6f5585ff..3b1c2f784 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -259,6 +259,7 @@ impl Player { storage, }; + player.mutate_with_update_context(|context| Avm2::load_player_globals(context))?; player.build_matrices(); player.audio.set_frame_rate(frame_rate); @@ -345,10 +346,6 @@ impl Player { AvmString::new(activation.context.gc_context, version_string).into(), EnumSet::empty(), ); - - drop(activation); - - Avm2::load_player_globals(context).unwrap(); }); self.build_matrices(); From 11ddccfa6ad6eb28e1b21e0dbc7586221e50f894 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 23:00:17 -0400 Subject: [PATCH 22/25] Remove the two-step initialization process and construct an ES4 class for `Object`, `Function`, and `Class`. This has some particularly annoying consequences for initialization order: notably, we can't actually create any ES4 classes using the standard machinery until after the three objects I just mentioned get created. Ergo, we have to create them through lower-level means, handing prototypes around, and then initialize AVM2's system prototypes list for it. When we start adding more system prototypes, we'll also have to fill the extras with blank objects and then slot them in as we create them. --- core/src/avm2.rs | 3 +- core/src/avm2/globals.rs | 284 +++++++++++++++-------------- core/src/avm2/globals/boolean.rs | 36 ++-- core/src/avm2/globals/class.rs | 59 ++++-- core/src/avm2/globals/function.rs | 69 +++++-- core/src/avm2/globals/int.rs | 36 ++-- core/src/avm2/globals/namespace.rs | 36 ++-- core/src/avm2/globals/number.rs | 36 ++-- core/src/avm2/globals/object.rs | 43 ++++- core/src/avm2/globals/string.rs | 36 ++-- core/src/avm2/globals/uint.rs | 36 ++-- 11 files changed, 431 insertions(+), 243 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index e694ecd9c..c180f147f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -2,11 +2,10 @@ use crate::avm2::activation::Activation; use crate::avm2::globals::SystemPrototypes; -use crate::avm2::object::{Object, TObject}; +use crate::avm2::object::{Object, ScriptObject, TObject}; use crate::avm2::scope::Scope; use crate::avm2::script::Script; use crate::avm2::script::TranslationUnit; -use crate::avm2::script_object::ScriptObject; use crate::avm2::value::Value; use crate::context::UpdateContext; use crate::tag_utils::SwfSlice; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 85ec0d9c8..5b8914ed8 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -51,6 +51,34 @@ pub struct SystemPrototypes<'gc> { pub namespace: Object<'gc>, } +impl<'gc> SystemPrototypes<'gc> { + /// Construct a minimal set of system prototypes necessary for + /// bootstrapping player globals. + /// + /// All other system prototypes aside from the three given here will be set + /// to the empty object also handed to this function. It is the caller's + /// responsibility to instantiate each class and replace the empty object + /// with that. + fn new( + object: Object<'gc>, + function: Object<'gc>, + class: Object<'gc>, + empty: Object<'gc>, + ) -> Self { + SystemPrototypes { + object, + function, + class, + string: empty, + boolean: empty, + number: empty, + int: empty, + uint: empty, + namespace: empty, + } + } +} + /// Add a free-function builtin to the global scope. fn function<'gc>( mc: MutationContext<'gc, '_>, @@ -69,39 +97,47 @@ fn function<'gc>( .unwrap() } -/// Add an ES3-style builtin to the global scope. -fn oldstyle_class<'gc>( +/// Add a class builtin with prototype methods to the global scope. +/// +/// Since the function has to return a normal prototype object in this case, we +/// have to construct a constructor to go along with it, as if we had called +/// `install_foreign_trait` with such a class. +fn dynamic_class<'gc>( mc: MutationContext<'gc, '_>, mut global_scope: Object<'gc>, - package: impl Into>, - name: impl Into>, - constr: NativeMethod<'gc>, - proto: Object<'gc>, - fn_proto: Object<'gc>, + constr: Object<'gc>, ) { - global_scope - .install_dynamic_property( - mc, - QName::new(Namespace::package(package), name), - FunctionObject::from_builtin_constr(mc, constr, proto, fn_proto) - .unwrap() - .into(), - ) - .unwrap(); + let name = constr + .as_class() + .expect("constrs have classes in them") + .read() + .name() + .clone(); + + global_scope.install_const(mc, name, 0, constr.into()); } /// Add a class builtin to the global scope. +/// +/// This function returns a prototype which may be stored in `SystemPrototypes`. fn class<'gc>( activation: &mut Activation<'_, 'gc, '_>, mut global: Object<'gc>, class_def: GcCell<'gc, Class<'gc>>, -) -> Result<(), Error> { +) -> Result, Error> { let class_trait = Trait::from_class(class_def); let global_scope = Scope::push_scope(global.get_scope(), global, activation.context.gc_context); + let mut constr = global + .install_foreign_trait(activation, class_trait, Some(global_scope), global)? + .coerce_to_object(activation)?; - global.install_foreign_trait(activation, class_trait, Some(global_scope), global)?; - - Ok(()) + constr + .get_property( + constr, + &QName::new(Namespace::public_namespace(), "prototype"), + activation, + )? + .coerce_to_object(activation) } /// Add a builtin constant to the global scope. @@ -115,131 +151,99 @@ fn constant<'gc>( global_scope.install_const(mc, QName::new(Namespace::package(package), name), 0, value) } -/// Construct a new global scope. -/// -/// This function returns both the global scope object, as well as all builtin -/// prototypes that other parts of the VM will need to use. -/// -/// Due to a limitation of our type system and our garbage collector, the -/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet. -/// As a result, global scope initialization is split into an "oldstyle phase" -/// and a "player-globals phase". This is the former phase, where we initialize -/// as much as we can without an `UpdateContext`. Note that not all -/// `SystemPrototypes` will be necessarily valid at this point in time, and -/// using them right away will result in objects of the wrong type. -pub fn construct_global_scope<'gc>( - mc: MutationContext<'gc, '_>, -) -> (Object<'gc>, SystemPrototypes<'gc>) { - let gs = ScriptObject::bare_object(mc); - - // public / root package - let object_proto = ScriptObject::bare_object(mc); - let fn_proto = function::create_proto(mc, object_proto); - let class_proto = class::create_proto(mc, object_proto, fn_proto); - let string_proto = string::create_proto(mc, object_proto, fn_proto); - let boolean_proto = boolean::create_proto(mc, object_proto, fn_proto); - let number_proto = number::create_proto(mc, object_proto, fn_proto); - let int_proto = int::create_proto(mc, object_proto, fn_proto); - let uint_proto = uint::create_proto(mc, object_proto, fn_proto); - let namespace_proto = namespace::create_proto(mc, object_proto, fn_proto); - - object::fill_proto(mc, object_proto, fn_proto); - - oldstyle_class( - mc, - gs, - "", - "Object", - object::constructor, - object_proto, - fn_proto, - ); - oldstyle_class( - mc, - gs, - "", - "Function", - function::constructor, - fn_proto, - fn_proto, - ); - oldstyle_class( - mc, - gs, - "", - "Class", - class::constructor, - class_proto, - fn_proto, - ); - oldstyle_class( - mc, - gs, - "", - "String", - string::constructor, - string_proto, - fn_proto, - ); - oldstyle_class( - mc, - gs, - "", - "Boolean", - boolean::constructor, - boolean_proto, - fn_proto, - ); - oldstyle_class( - mc, - gs, - "", - "Number", - number::constructor, - number_proto, - fn_proto, - ); - oldstyle_class(mc, gs, "", "int", int::constructor, int_proto, fn_proto); - oldstyle_class(mc, gs, "", "uint", uint::constructor, uint_proto, fn_proto); - oldstyle_class( - mc, - gs, - "", - "Namespace", - namespace::constructor, - namespace_proto, - fn_proto, - ); - function(mc, gs, "", "trace", trace, fn_proto); - constant(mc, gs, "", "undefined", Value::Undefined); - constant(mc, gs, "", "null", Value::Null); - constant(mc, gs, "", "NaN", NAN.into()); - constant(mc, gs, "", "Infinity", f64::INFINITY.into()); - - let system_prototypes = SystemPrototypes { - object: object_proto, - function: fn_proto, - class: class_proto, - string: string_proto, - boolean: boolean_proto, - number: number_proto, - int: int_proto, - uint: uint_proto, - namespace: namespace_proto, - }; - - (gs, system_prototypes) -} - /// Initialize all remaining builtin classes. /// -/// Due to a limitation of our type system and our garbage collector, the -/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet. -/// As a result, global scope initialization is split into an "oldstyle phase" -/// and a "player-globals phase". This is the latter phase. +/// This should be called only once, to construct the global scope of the +/// player. It will return a list of prototypes it has created, which should be +/// stored on the AVM. pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Result<(), Error> { let gs = activation.avm2().globals(); + // public / root package + let object_proto = object::create_proto(activation); + let (function_constr, fn_proto) = function::create_class(activation, object_proto); + let (class_constr, class_proto) = class::create_class(activation, object_proto, fn_proto); + + let object_constr = object::fill_proto(activation.context.gc_context, object_proto, fn_proto); + + dynamic_class(activation.context.gc_context, gs, object_constr); + dynamic_class(activation.context.gc_context, gs, function_constr); + dynamic_class(activation.context.gc_context, gs, class_constr); + + // At this point, we need at least a partial set of system prototypes in + // order to continue initializing the player. The rest of the prototypes + // are set to a bare object until we have a chance to initialize them. + activation.context.avm2.system_prototypes = Some(SystemPrototypes::new( + object_proto, + fn_proto, + class_proto, + ScriptObject::bare_object(activation.context.gc_context), + )); + + // Even sillier: for the sake of clarity and the borrow checker we need to + // clone the prototypes list and modify it outside of the activation. This + // also has the side effect that none of these classes can get at each + // other from the activation they're handed. + let mut sp = activation.context.avm2.system_prototypes.clone().unwrap(); + + sp.string = class( + activation, + gs, + string::create_class(activation.context.gc_context), + )?; + sp.boolean = class( + activation, + gs, + boolean::create_class(activation.context.gc_context), + )?; + sp.number = class( + activation, + gs, + number::create_class(activation.context.gc_context), + )?; + sp.int = class( + activation, + gs, + int::create_class(activation.context.gc_context), + )?; + sp.uint = class( + activation, + gs, + uint::create_class(activation.context.gc_context), + )?; + sp.namespace = class( + activation, + gs, + namespace::create_class(activation.context.gc_context), + )?; + + activation.context.avm2.system_prototypes = Some(sp); + + function( + activation.context.gc_context, + gs, + "", + "trace", + trace, + fn_proto, + ); + constant( + activation.context.gc_context, + gs, + "", + "undefined", + Value::Undefined, + ); + constant(activation.context.gc_context, gs, "", "null", Value::Null); + constant(activation.context.gc_context, gs, "", "NaN", NAN.into()); + constant( + activation.context.gc_context, + gs, + "", + "Infinity", + f64::INFINITY.into(), + ); + // package `flash.events` class( activation, diff --git a/core/src/avm2/globals/boolean.rs b/core/src/avm2/globals/boolean.rs index f0519e235..781709d18 100644 --- a/core/src/avm2/globals/boolean.rs +++ b/core/src/avm2/globals/boolean.rs @@ -1,13 +1,16 @@ //! `Boolean` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `Boolean` -pub fn constructor<'gc>( +/// Implements `Boolean`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("Boolean constructor is a stub.".into()) } -/// Construct `Boolean.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `Boolean`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `Boolean`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "Boolean"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index 9eb0f5027..e7429ac50 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -1,16 +1,19 @@ //! `Class` builtin/prototype use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; +use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; -/// Implements `Class` +/// Implements `Class`'s instance initializer. /// /// Notably, you cannot construct new classes this way, so this returns an /// error. -pub fn constructor<'gc>( +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -18,11 +21,45 @@ pub fn constructor<'gc>( Err("Classes cannot be constructed.".into()) } -/// Construct `Class.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implement's `Class`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Err("Classes cannot be constructed.".into()) +} + +/// Construct `Class` and `Class.prototype`, respectively. +pub fn create_class<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + super_proto: Object<'gc>, + fn_proto: Object<'gc>, +) -> (Object<'gc>, Object<'gc>) { + let class_class = Class::new( + QName::new(Namespace::public_namespace(), "Class"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + activation.context.gc_context, + ); + + let globals = activation.avm2().globals(); + let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); + let proto = ScriptObject::prototype( + activation.context.gc_context, + super_proto, + class_class, + Some(scope), + ); + + let constr = FunctionObject::from_builtin_constr( + activation.context.gc_context, + instance_init, + proto, + fn_proto, + ) + .unwrap(); + + (constr, proto) } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index cbf22e9ca..f611d4989 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -1,14 +1,25 @@ //! Function builtin and prototype use crate::avm2::activation::Activation; +use crate::avm2::class::Class; +use crate::avm2::method::Method; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; +use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; -/// Implements `Function` -pub fn constructor<'gc>( +/// Implements `Function`'s instance initializer. +pub fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Implements `Function`'s class initializer. +pub fn class_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -38,22 +49,42 @@ fn call<'gc>( } } -/// Partially construct `Function.prototype`. -/// -/// `__proto__` and other cross-linked properties of this object will *not* -/// be defined here. The caller of this function is responsible for linking -/// them in order to obtain a valid ECMAScript `Function` prototype. The -/// returned object is also a bare object, which will need to be linked into -/// the prototype of `Object`. -pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> { - let mut function_proto = ScriptObject::object(gc_context, proto); - - function_proto.install_method( - gc_context, - QName::new(Namespace::as3_namespace(), "call"), - 0, - FunctionObject::from_builtin(gc_context, call, function_proto), +/// Construct `Function` and `Function.prototype`, respectively. +pub fn create_class<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + proto: Object<'gc>, +) -> (Object<'gc>, Object<'gc>) { + let function_class = Class::new( + QName::new(Namespace::public_namespace(), "Function"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + activation.context.gc_context, ); - function_proto + let globals = activation.avm2().globals(); + let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); + let mut function_proto = ScriptObject::prototype( + activation.context.gc_context, + proto, + function_class, + Some(scope), + ); + + function_proto.install_method( + activation.context.gc_context, + QName::new(Namespace::as3_namespace(), "call"), + 0, + FunctionObject::from_builtin(activation.context.gc_context, call, function_proto), + ); + + let constr = FunctionObject::from_builtin_constr( + activation.context.gc_context, + instance_init, + proto, + function_proto, + ) + .unwrap(); + + (constr, function_proto) } diff --git a/core/src/avm2/globals/int.rs b/core/src/avm2/globals/int.rs index f73f3c326..136becca8 100644 --- a/core/src/avm2/globals/int.rs +++ b/core/src/avm2/globals/int.rs @@ -1,13 +1,16 @@ //! `int` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `int` -pub fn constructor<'gc>( +/// Implements `int`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("int constructor is a stub.".into()) } -/// Construct `int.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `int`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `int`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "int"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/namespace.rs b/core/src/avm2/globals/namespace.rs index 61798a607..954618781 100644 --- a/core/src/avm2/globals/namespace.rs +++ b/core/src/avm2/globals/namespace.rs @@ -1,13 +1,16 @@ //! `Namespace` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `Namespace` -pub fn constructor<'gc>( +/// Implements `Namespace`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("Namespace constructor is a stub.".into()) } -/// Construct `Namespace.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `Namespace`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `Namespace`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "Namespace"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/number.rs b/core/src/avm2/globals/number.rs index dfaccc4ab..cb3937d1c 100644 --- a/core/src/avm2/globals/number.rs +++ b/core/src/avm2/globals/number.rs @@ -1,13 +1,16 @@ //! `Number` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `Number` -pub fn constructor<'gc>( +/// Implements `Number`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("Number constructor is a stub.".into()) } -/// Construct `Number.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `Number`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `Number`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "Number"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index 730399e80..db0c1e4e3 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -1,14 +1,26 @@ //! Object builtin and prototype use crate::avm2::activation::Activation; +use crate::avm2::class::Class; +use crate::avm2::method::Method; use crate::avm2::names::{Namespace, QName}; -use crate::avm2::object::{FunctionObject, Object, TObject}; +use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; +use crate::avm2::scope::Scope; use crate::avm2::value::Value; use crate::avm2::Error; use gc_arena::MutationContext; -/// Implements `Object` -pub fn constructor<'gc>( +/// Implements `Object`'s instance initializer. +pub fn instance_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Implements `Object`'s class initializer +pub fn class_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -140,7 +152,26 @@ pub fn set_property_is_enumerable<'gc>( Ok(Value::Undefined) } -/// Partially construct `Object.prototype`. +/// Create object prototype. +/// +/// This function creates a suitable class and object prototype attached to it, +/// but does not actually fill it with methods. That requires a valid function +/// prototype, and is thus done by `fill_proto` below. +pub fn create_proto<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Object<'gc> { + let object_class = Class::new( + QName::new(Namespace::public_namespace(), "Object"), + None, + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + activation.context.gc_context, + ); + + let globals = activation.avm2().globals(); + let scope = Scope::push_scope(globals.get_scope(), globals, activation.context.gc_context); + ScriptObject::bare_prototype(activation.context.gc_context, object_class, Some(scope)) +} + +/// Finish constructing `Object.prototype`, and also construct `Object`. /// /// `__proto__` and other cross-linked properties of this object will *not* /// be defined here. The caller of this function is responsible for linking @@ -153,7 +184,7 @@ pub fn fill_proto<'gc>( gc_context: MutationContext<'gc, '_>, mut object_proto: Object<'gc>, fn_proto: Object<'gc>, -) { +) -> Object<'gc> { object_proto.install_method( gc_context, QName::new(Namespace::public_namespace(), "toString"), @@ -196,4 +227,6 @@ pub fn fill_proto<'gc>( 0, FunctionObject::from_builtin(gc_context, set_property_is_enumerable, fn_proto), ); + + FunctionObject::from_builtin_constr(gc_context, instance_init, object_proto, fn_proto).unwrap() } diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index 9b822d3a3..44ef55b4a 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -1,13 +1,16 @@ //! `String` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `String` -pub fn constructor<'gc>( +/// Implements `String`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("String constructor is a stub.".into()) } -/// Construct `String.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `String`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `String`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "String"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } diff --git a/core/src/avm2/globals/uint.rs b/core/src/avm2/globals/uint.rs index 5755d448b..f437b19a9 100644 --- a/core/src/avm2/globals/uint.rs +++ b/core/src/avm2/globals/uint.rs @@ -1,13 +1,16 @@ //! `uint` impl use crate::avm2::activation::Activation; -use crate::avm2::object::{Object, ScriptObject}; +use crate::avm2::class::Class; +use crate::avm2::method::Method; +use crate::avm2::names::{Namespace, QName}; +use crate::avm2::object::Object; use crate::avm2::value::Value; use crate::avm2::Error; -use gc_arena::MutationContext; +use gc_arena::{GcCell, MutationContext}; -/// Implements `uint` -pub fn constructor<'gc>( +/// Implements `uint`'s instance initializer. +pub fn instance_init<'gc>( _activation: &mut Activation<'_, 'gc, '_>, _this: Option>, _args: &[Value<'gc>], @@ -15,11 +18,22 @@ pub fn constructor<'gc>( Err("uint constructor is a stub.".into()) } -/// Construct `uint.prototype`. -pub fn create_proto<'gc>( - mc: MutationContext<'gc, '_>, - super_proto: Object<'gc>, - _fn_proto: Object<'gc>, -) -> Object<'gc> { - ScriptObject::object(mc, super_proto) +/// Implements `uint`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Construct `uint`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + Class::new( + QName::new(Namespace::package(""), "uint"), + Some(QName::new(Namespace::public_namespace(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ) } From 32aad6176bbf07a9fc5eeb0b64bb0150095527e3 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Tue, 4 Aug 2020 23:02:03 -0400 Subject: [PATCH 23/25] `FunctionObject::from_builtin_constr` should pull the scope and class off of it's given prototype and copy it onto the constructor function it returns. --- core/src/avm2/object/function_object.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index beb326f34..f83d7ae9b 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -195,10 +195,15 @@ impl<'gc> FunctionObject<'gc> { mut prototype: Object<'gc>, fn_proto: Object<'gc>, ) -> Result, Error> { + let scope = prototype.get_scope(); + let class = prototype + .as_class() + .map(|c| ScriptObjectClass::ClassConstructor(c, scope)) + .unwrap_or(ScriptObjectClass::NoClass); let mut base: Object<'gc> = FunctionObject(GcCell::allocate( mc, FunctionObjectData { - base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), + base: ScriptObjectData::base_new(Some(fn_proto), class), exec: Some(Executable::from_method(constr.into(), None, None, mc)), }, )) From 7b7f0b20e6efa14941c4d78d4d8b0895238e40b8 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 14 Aug 2020 20:52:09 -0400 Subject: [PATCH 24/25] Consolidate all of our copied `CollectWrapper`s. --- core/src/avm2/class.rs | 5 +---- core/src/avm2/method.rs | 5 +---- core/src/avm2/script.rs | 5 +---- core/src/avm2/trait.rs | 5 +---- core/src/collect.rs | 7 +++++++ core/src/html/layout.rs | 5 +---- core/src/lib.rs | 1 + 7 files changed, 13 insertions(+), 20 deletions(-) create mode 100644 core/src/collect.rs diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index dacffa6e5..81cc8acca 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -6,14 +6,11 @@ use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; use crate::avm2::string::AvmString; use crate::avm2::{Avm2, Error}; +use crate::collect::CollectWrapper; use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, GcCell, MutationContext}; use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance}; -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - /// All possible attributes for a given class. #[derive(EnumSetType, Debug)] pub enum ClassAttributes { diff --git a/core/src/avm2/method.rs b/core/src/avm2/method.rs index 70b6a64be..c0ebd6dfd 100644 --- a/core/src/avm2/method.rs +++ b/core/src/avm2/method.rs @@ -5,15 +5,12 @@ use crate::avm2::object::Object; use crate::avm2::script::TranslationUnit; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::collect::CollectWrapper; use gc_arena::{Collect, CollectionContext, Gc, MutationContext}; use std::fmt; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody}; -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - /// Represents a function defined in Ruffle's code. /// /// Parameters are as follows: diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index fdbb5cfdf..b78c76fd4 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -5,16 +5,13 @@ use crate::avm2::method::{BytecodeMethod, Method}; use crate::avm2::r#trait::Trait; use crate::avm2::string::AvmString; use crate::avm2::{Avm2, Error}; +use crate::collect::CollectWrapper; use fnv::FnvHashMap; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use std::mem::drop; use std::rc::Rc; use swf::avm2::types::{AbcFile, Index, Script as AbcScript}; -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - #[derive(Copy, Clone, Debug, Collect)] #[collect(no_drop)] pub struct TranslationUnit<'gc>(GcCell<'gc, TranslationUnitData<'gc>>); diff --git a/core/src/avm2/trait.rs b/core/src/avm2/trait.rs index b7711df23..66434e40e 100644 --- a/core/src/avm2/trait.rs +++ b/core/src/avm2/trait.rs @@ -6,14 +6,11 @@ use crate::avm2::names::{Multiname, QName}; use crate::avm2::script::TranslationUnit; use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::{Avm2, Error}; +use crate::collect::CollectWrapper; use enumset::{EnumSet, EnumSetType}; use gc_arena::{Collect, GcCell, MutationContext}; use swf::avm2::types::{Trait as AbcTrait, TraitKind as AbcTraitKind}; -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - /// All attributes a trait can have. #[derive(Debug, EnumSetType)] pub enum TraitAttributes { diff --git a/core/src/collect.rs b/core/src/collect.rs new file mode 100644 index 000000000..1aaf40164 --- /dev/null +++ b/core/src/collect.rs @@ -0,0 +1,7 @@ +//! Collect Wrapper Utility + +use gc_arena::Collect; + +#[derive(Clone, Debug, Collect)] +#[collect(require_static)] +pub struct CollectWrapper(pub T); diff --git a/core/src/html/layout.rs b/core/src/html/layout.rs index 6d12eca24..707d4c381 100644 --- a/core/src/html/layout.rs +++ b/core/src/html/layout.rs @@ -1,5 +1,6 @@ //! Layout box structure +use crate::collect::CollectWrapper; use crate::context::UpdateContext; use crate::drawing::Drawing; use crate::font::{EvalParameters, Font}; @@ -552,10 +553,6 @@ pub struct LayoutBox<'gc> { content: LayoutContent<'gc>, } -#[derive(Clone, Debug, Collect)] -#[collect(require_static)] -pub struct CollectWrapper(T); - /// Represents different content modes of a given `LayoutBox`. /// /// Currently, a `LayoutBox` can contain `Text`, `Bullet`s, or a `Drawing`. diff --git a/core/src/lib.rs b/core/src/lib.rs index 01c8118d6..0acaea6ee 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,6 +14,7 @@ mod avm1; mod avm2; mod bounding_box; mod character; +mod collect; pub mod color_transform; mod context; mod drawing; From 4c824fcefefafc5522710dfafe091e7eb62423b8 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 14 Aug 2020 21:20:41 -0400 Subject: [PATCH 25/25] Rename `trait.rs` to `traits.rs` to avoid the use of reserved keyword syntax. --- core/src/avm2.rs | 2 +- core/src/avm2/class.rs | 2 +- core/src/avm2/globals.rs | 2 +- core/src/avm2/object.rs | 2 +- core/src/avm2/object/function_object.rs | 2 +- core/src/avm2/object/namespace_object.rs | 2 +- core/src/avm2/object/primitive_object.rs | 2 +- core/src/avm2/object/script_object.rs | 2 +- core/src/avm2/script.rs | 2 +- core/src/avm2/{trait.rs => traits.rs} | 0 10 files changed, 9 insertions(+), 9 deletions(-) rename core/src/avm2/{trait.rs => traits.rs} (100%) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index c180f147f..373bf178f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -36,7 +36,7 @@ mod scope; mod script; mod slot; mod string; -mod r#trait; +mod traits; mod value; /// Boxed error alias. diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 81cc8acca..ed193e429 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -2,9 +2,9 @@ use crate::avm2::method::Method; use crate::avm2::names::{Multiname, Namespace, QName}; -use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::script::TranslationUnit; use crate::avm2::string::AvmString; +use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::{Avm2, Error}; use crate::collect::CollectWrapper; use enumset::{EnumSet, EnumSetType}; diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 5b8914ed8..8747c4373 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -5,9 +5,9 @@ use crate::avm2::class::Class; use crate::avm2::method::NativeMethod; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject}; -use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index a116e05e0..51f83d6dc 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -4,9 +4,9 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::function::Executable; use crate::avm2::names::{Multiname, Namespace, QName}; -use crate::avm2::r#trait::{Trait, TraitKind}; use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; +use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::value::{Hint, Value}; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; diff --git a/core/src/avm2/object/function_object.rs b/core/src/avm2/object/function_object.rs index f83d7ae9b..3020067bb 100644 --- a/core/src/avm2/object/function_object.rs +++ b/core/src/avm2/object/function_object.rs @@ -7,9 +7,9 @@ use crate::avm2::method::{Method, NativeMethod}; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::script_object::{ScriptObject, ScriptObjectClass, ScriptObjectData}; use crate::avm2::object::{Object, ObjectPtr, TObject}; -use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use crate::impl_avm2_custom_object; diff --git a/core/src/avm2/object/namespace_object.rs b/core/src/avm2/object/namespace_object.rs index 7fcdb325b..f8761b605 100644 --- a/core/src/avm2/object/namespace_object.rs +++ b/core/src/avm2/object/namespace_object.rs @@ -6,8 +6,8 @@ use crate::avm2::class::Class; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::object::{Object, ObjectPtr, TObject}; -use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use crate::impl_avm2_custom_object; diff --git a/core/src/avm2/object/primitive_object.rs b/core/src/avm2/object/primitive_object.rs index c0c433782..62bf14cd6 100644 --- a/core/src/avm2/object/primitive_object.rs +++ b/core/src/avm2/object/primitive_object.rs @@ -5,9 +5,9 @@ use crate::avm2::class::Class; use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::object::{Object, ObjectPtr, TObject}; -use crate::avm2::r#trait::Trait; use crate::avm2::scope::Scope; use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use crate::impl_avm2_custom_object; diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index c799bf559..2d03c345f 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -7,11 +7,11 @@ use crate::avm2::names::{Namespace, QName}; use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::property::Property; use crate::avm2::property_map::PropertyMap; -use crate::avm2::r#trait::Trait; use crate::avm2::return_value::ReturnValue; use crate::avm2::scope::Scope; use crate::avm2::slot::Slot; use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use gc_arena::{Collect, GcCell, MutationContext}; diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index b78c76fd4..0b5d76e84 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -2,8 +2,8 @@ use crate::avm2::class::Class; use crate::avm2::method::{BytecodeMethod, Method}; -use crate::avm2::r#trait::Trait; use crate::avm2::string::AvmString; +use crate::avm2::traits::Trait; use crate::avm2::{Avm2, Error}; use crate::collect::CollectWrapper; use fnv::FnvHashMap; diff --git a/core/src/avm2/trait.rs b/core/src/avm2/traits.rs similarity index 100% rename from core/src/avm2/trait.rs rename to core/src/avm2/traits.rs