From 52e8534abaac53550ea3a6a78266b1b7b98a3b94 Mon Sep 17 00:00:00 2001 From: Lord-McSweeney Date: Sun, 25 Aug 2024 17:17:56 -0700 Subject: [PATCH] avm2: Ensure global object always has correct vtable --- core/src/avm2.rs | 13 +- core/src/avm2/amf.rs | 3 +- core/src/avm2/class.rs | 18 +- core/src/avm2/function.rs | 6 +- core/src/avm2/globals.rs | 308 ++++++++++++++++---------- core/src/avm2/globals/array.rs | 4 +- core/src/avm2/globals/avmplus.rs | 2 +- core/src/avm2/globals/boolean.rs | 4 +- core/src/avm2/globals/date.rs | 4 +- core/src/avm2/globals/global_scope.rs | 29 +-- core/src/avm2/globals/int.rs | 4 +- core/src/avm2/globals/namespace.rs | 4 +- core/src/avm2/globals/number.rs | 4 +- core/src/avm2/globals/string.rs | 4 +- core/src/avm2/globals/uint.rs | 4 +- core/src/avm2/globals/vector.rs | 8 +- core/src/avm2/object.rs | 18 -- core/src/avm2/object/script_object.rs | 14 -- core/src/avm2/optimize.rs | 4 +- core/src/avm2/script.rs | 154 +++++-------- core/src/avm2/value.rs | 4 +- core/src/avm2/vector.rs | 2 +- core/src/avm2/vtable.rs | 22 -- 23 files changed, 300 insertions(+), 337 deletions(-) diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 189bd1bbd..aa8dfd87f 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use crate::avm2::class::AllocatorFn; use crate::avm2::error::make_error_1107; -use crate::avm2::globals::SystemClasses; +use crate::avm2::globals::{SystemClassDefs, SystemClasses}; use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::scope::ScopeChain; use crate::avm2::script::{Script, TranslationUnit}; @@ -126,6 +126,9 @@ pub struct Avm2<'gc> { /// System classes. system_classes: Option>, + /// System class definitions. + system_class_defs: Option>, + /// Top-level global object. It contains most top-level types (Object, Class) and functions. /// However, it's not strictly defined which items end up there. toplevel_global_object: Option>, @@ -221,6 +224,7 @@ impl<'gc> Avm2<'gc> { playerglobals_domain, stage_domain, system_classes: None, + system_class_defs: None, toplevel_global_object: None, public_namespace_base_version: Namespace::package("", ApiVersion::AllVersions, context), @@ -289,6 +293,13 @@ impl<'gc> Avm2<'gc> { self.system_classes.as_ref().unwrap() } + /// Return the current set of system class definitions. + /// + /// This function panics if the interpreter has not yet been initialized. + pub fn class_defs(&self) -> &SystemClassDefs<'gc> { + self.system_class_defs.as_ref().unwrap() + } + pub fn toplevel_global_object(&self) -> Option> { self.toplevel_global_object } diff --git a/core/src/avm2/amf.rs b/core/src/avm2/amf.rs index 3f2453d89..0303d6abb 100644 --- a/core/src/avm2/amf.rs +++ b/core/src/avm2/amf.rs @@ -118,8 +118,7 @@ pub fn serialize_value<'gc>( }) .collect(); - let val_type = val_type - .unwrap_or(activation.avm2().classes().object.inner_class_definition()); + let val_type = val_type.unwrap_or(activation.avm2().class_defs().object); let name = class_to_alias(activation, val_type); Some(AmfValue::VectorObject(obj_vec, name, vec.is_fixed())) diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index a339b1c0d..f53c0d954 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -367,7 +367,7 @@ impl<'gc> Class<'gc> { ), object_vector_i_class.instance_init(), object_vector_c_class.instance_init(), - context.avm2.classes().class.inner_class_definition(), + context.avm2.class_defs().class, mc, ); @@ -573,7 +573,7 @@ impl<'gc> Class<'gc> { ClassData { name: c_name, param: None, - super_class: Some(activation.avm2().classes().class.inner_class_definition()), + super_class: Some(activation.avm2().class_defs().class), attributes: ClassAttributes::FINAL, protected_namespace, direct_interfaces: Vec::new(), @@ -871,7 +871,7 @@ impl<'gc> Class<'gc> { ClassData { name: c_name, param: None, - super_class: Some(activation.avm2().classes().class.inner_class_definition()), + super_class: Some(activation.avm2().class_defs().class), attributes: ClassAttributes::FINAL, protected_namespace: None, direct_interfaces: Vec::new(), @@ -1228,6 +1228,10 @@ impl<'gc> Class<'gc> { Ref::map(self.0.read(), |c| &c.traits) } + pub fn set_traits(&self, mc: &Mutation<'gc>, traits: Vec>) { + self.0.write(mc).traits = traits; + } + /// Get this class's instance allocator. /// /// If `None`, then you should use the instance allocator of the superclass @@ -1311,6 +1315,10 @@ impl<'gc> Class<'gc> { self.0.write(mc).linked_class = ClassLink::LinkToClass(c_class); } + pub fn is_c_class(self) -> bool { + matches!(self.0.read().linked_class, ClassLink::LinkToInstance(_)) + } + pub fn i_class(self) -> Option> { if let ClassLink::LinkToInstance(i_class) = self.0.read().linked_class { Some(i_class) @@ -1324,4 +1332,8 @@ impl<'gc> Class<'gc> { self.0.write(mc).linked_class = ClassLink::LinkToInstance(i_class); } + + pub fn is_i_class(self) -> bool { + matches!(self.0.read().linked_class, ClassLink::LinkToClass(_)) + } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index ef8c0fb6b..af143973b 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -268,9 +268,9 @@ pub fn display_function<'gc>( .map(|b| Gc::ptr_eq(b, *method)) .unwrap_or(false) { - if bound_class.c_class().is_none() { - // If the associated class has no c_class, it is a c_class, - // and the instance initializer is the class initializer. + if bound_class.is_c_class() { + // If the associated class is a c_class, its initializer + // method is a class initializer. output.push_utf8("cinit"); } // We purposely do nothing for instance initializers diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index c00268ad7..8256729c3 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -2,14 +2,17 @@ use crate::avm2::activation::Activation; use crate::avm2::api_version::ApiVersion; use crate::avm2::class::Class; use crate::avm2::domain::Domain; +use crate::avm2::multiname::Multiname; use crate::avm2::object::{ClassObject, ScriptObject, TObject}; use crate::avm2::scope::{Scope, ScopeChain}; use crate::avm2::script::Script; +use crate::avm2::traits::Trait; +use crate::avm2::value::Value; +use crate::avm2::vtable::VTable; use crate::avm2::Avm2; use crate::avm2::Error; use crate::avm2::Namespace; use crate::avm2::QName; -use crate::string::AvmString; use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream}; use gc_arena::Collect; use std::sync::Arc; @@ -66,7 +69,6 @@ pub struct SystemClasses<'gc> { pub number: ClassObject<'gc>, pub int: ClassObject<'gc>, pub uint: ClassObject<'gc>, - pub void_def: Class<'gc>, pub namespace: ClassObject<'gc>, pub array: ClassObject<'gc>, pub movieclip: ClassObject<'gc>, @@ -179,6 +181,15 @@ pub struct SystemClasses<'gc> { pub textrun: ClassObject<'gc>, } +#[derive(Clone, Collect)] +#[collect(no_drop)] +pub struct SystemClassDefs<'gc> { + pub object: Class<'gc>, + pub class: Class<'gc>, + pub function: Class<'gc>, + pub void: Class<'gc>, +} + impl<'gc> SystemClasses<'gc> { /// Construct a minimal set of system classes necessary for bootstrapping /// player globals. @@ -198,7 +209,6 @@ impl<'gc> SystemClasses<'gc> { number: object, int: object, uint: object, - void_def: object.inner_class_definition(), namespace: object, array: object, movieclip: object, @@ -311,41 +321,37 @@ impl<'gc> SystemClasses<'gc> { } } +impl<'gc> SystemClassDefs<'gc> { + fn new(object: Class<'gc>, class: Class<'gc>, function: Class<'gc>, void: Class<'gc>) -> Self { + SystemClassDefs { + object, + class, + function, + void, + } + } +} + /// Looks up a function defined in the script domain, and defines it on the global object. /// /// This expects the looked-up value to be a function. fn define_fn_on_global<'gc>( activation: &mut Activation<'_, 'gc>, - package: impl Into>, name: &'static str, script: Script<'gc>, ) { let (_, global, domain) = script.init(); let qname = QName::new( - Namespace::package( - package, - ApiVersion::AllVersions, - &mut activation.borrow_gc(), - ), + Namespace::package("", ApiVersion::AllVersions, &mut activation.borrow_gc()), name, ); let func = domain .get_defined_value(activation, qname) .expect("Function being defined on global should be defined in domain!"); - global.install_const_late( - activation.context.gc_context, - qname, - func, - activation - .avm2() - .classes() - .function - .inner_class_definition(), - ); - script - .global_class() - .define_constant_function_instance_trait(activation, qname, func); + global + .init_property(&qname.into(), func, activation) + .expect("Should set property"); } /// Add a fully-formed class object builtin to the global scope. @@ -361,15 +367,10 @@ fn dynamic_class<'gc>( let class = class_object.inner_class_definition(); let name = class.name(); - global.install_const_late( - activation.context.gc_context, - name, - class_object.into(), - class_object.instance_class(), - ); - script - .global_class() - .define_constant_class_instance_trait(activation, name, class_object); + global + .init_property(&name.into(), class_object.into(), activation) + .expect("Should set property"); + domain.export_definition(name, script, activation.context.gc_context) } @@ -398,24 +399,19 @@ fn class<'gc>( let class_name = class_def.name(); let class_object = ClassObject::from_class(activation, class_def, super_class)?; - global.install_const_late( - mc, - class_name, - class_object.into(), - class_object.instance_class(), - ); - script.global_class().define_constant_class_instance_trait( - activation, - class_name, - class_object, - ); + + global + .init_property(&class_name.into(), class_object.into(), activation) + .expect("Should set property"); + domain.export_definition(class_name, script, mc); domain.export_class(class_name, class_def, mc); Ok(class_object) } fn vector_class<'gc>( - param_class: Option>, + param_class: Option>, + class_def: Class<'gc>, legacy_name: &'static str, script: Script<'gc>, activation: &mut Activation<'_, 'gc>, @@ -423,12 +419,9 @@ fn vector_class<'gc>( let mc = activation.context.gc_context; let (_, global, mut domain) = script.init(); - let param_class = param_class.map(|c| c.inner_class_definition()); - let vector_cls = class( - vector::create_builtin_class(activation, param_class), - script, - activation, - )?; + let object_class = activation.avm2().classes().object; + + let vector_cls = ClassObject::from_class(activation, class_def, Some(object_class))?; let generic_vector = activation.avm2().classes().generic_vector; generic_vector.add_application(mc, param_class, vector_cls); @@ -436,15 +429,11 @@ fn vector_class<'gc>( generic_cls.add_application(mc, param_class, vector_cls.inner_class_definition()); let legacy_name = QName::new(activation.avm2().vector_internal_namespace, legacy_name); - global.install_const_late( - mc, - legacy_name, - vector_cls.into(), - vector_cls.instance_class(), - ); - script - .global_class() - .define_constant_class_instance_trait(activation, legacy_name, vector_cls); + + global + .init_property(&legacy_name.into(), vector_cls.into(), activation) + .expect("Should set property"); + domain.export_definition(legacy_name, script, mc); Ok(vector_cls) } @@ -477,7 +466,7 @@ pub fn load_player_globals<'gc>( // This part of global initialization is very complicated, because // everything has to circularly reference everything else: // - // - Object is an instance of itself, as well as it's prototype + // - Object is an instance of itself, as well as its prototype // - All other types are instances of Class, which is an instance of // itself // - Function's prototype is an instance of itself @@ -506,21 +495,110 @@ pub fn load_player_globals<'gc>( // Function is more of a "normal" class than the other two, so we can create it normally. let fn_classdef = function::create_class(activation, object_i_class, class_i_class); - // Do the same for the global class - let global_classdef = global_scope::create_class(activation, object_i_class, class_i_class); + // void doesn't have a ClassObject + let void_def = void::create_class(activation); // Register the classes in the domain, now (except for the global class) domain.export_class(object_i_class.name(), object_i_class, mc); domain.export_class(class_i_class.name(), class_i_class, mc); domain.export_class(fn_classdef.name(), fn_classdef, mc); + domain.export_class(void_def.name(), void_def, mc); + + activation.context.avm2.system_class_defs = Some(SystemClassDefs::new( + object_i_class, + class_i_class, + fn_classdef, + void_def, + )); + + let string_class = string::create_class(activation); + let boolean_class = boolean::create_class(activation); + let number_class = number::create_class(activation); + let int_class = int::create_class(activation); + let uint_class = uint::create_class(activation); + let namespace_class = namespace::create_class(activation); + let array_class = array::create_class(activation); + let vector_generic_class = vector::create_generic_class(activation); + let date_class = date::create_class(activation); + + let vector_int_class = vector::create_builtin_class(activation, Some(int_class)); + let vector_uint_class = vector::create_builtin_class(activation, Some(uint_class)); + let vector_number_class = vector::create_builtin_class(activation, Some(number_class)); + let vector_object_class = vector::create_builtin_class(activation, None); + + let public_ns = activation.avm2().public_namespace_base_version; + let vector_public_ns = activation.avm2().vector_public_namespace; + let vector_internal_ns = activation.avm2().vector_internal_namespace; + + // Unfortunately we need to specify the global traits manually, at least until + // all the builtin classes are defined in AS. + let mut global_traits = Vec::new(); + + let class_trait_list = &[ + (public_ns, "Object", object_i_class), + (public_ns, "Class", class_i_class), + (public_ns, "Function", fn_classdef), + (public_ns, "String", string_class), + (public_ns, "Boolean", boolean_class), + (public_ns, "Number", number_class), + (public_ns, "int", int_class), + (public_ns, "uint", uint_class), + (public_ns, "Namespace", namespace_class), + (public_ns, "Array", array_class), + (public_ns, "Date", date_class), + (vector_public_ns, "Vector", vector_generic_class), + (vector_internal_ns, "Vector$int", vector_int_class), + (vector_internal_ns, "Vector$uint", vector_uint_class), + (vector_internal_ns, "Vector$double", vector_number_class), + (vector_internal_ns, "Vector$object", vector_object_class), + ]; + + // "trace" is the only builtin function not defined on the toplevel global object + let function_trait_list = &[ + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "escape", + "unescape", + "isXMLName", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + ]; + + for (namespace, name, class) in class_trait_list { + let qname = QName::new(*namespace, *name); + + global_traits.push(Trait::from_class(qname, *class)); + } + + for function_name in function_trait_list { + let qname = QName::new(public_ns, *function_name); + + // FIXME: These should be TraitKind::Methods, to match how they are when + // defined on the AS global object, but we don't have the actual Methods + // right now. + global_traits.push(Trait::from_const( + qname, + Multiname::new(public_ns, "Function"), + Some(Value::Null), + )); + } + + // Create the builtin globals' classdef + let global_classdef = global_scope::create_class(activation, global_traits); // Initialize the global object. This gives it a temporary vtable until the - // global ClassObject is constructed and we have the true vtable. + // global ClassObject is constructed and we have the true vtable; nothing actually + // operates on the global object until it gets its true vtable, so this should + // be fine. let globals = ScriptObject::custom_object(mc, global_classdef, None, global_classdef.vtable()); - // Initialize the script - let script = Script::empty_script(mc, globals, domain); + globals.install_instance_slots(mc); - let gs = ScopeChain::new(domain).chain(mc, &[Scope::new(globals)]); + let scope = ScopeChain::new(domain); + let gs = scope.chain(mc, &[Scope::new(globals)]); activation.set_outer(gs); let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?; @@ -577,16 +655,26 @@ pub fn load_player_globals<'gc>( let fn_proto = fn_class.construct(activation, &[])?; fn_class.link_prototype(activation, fn_proto)?; - // Construct the global class. - let global_class = ClassObject::from_class(activation, global_classdef, Some(object_class))?; + // Object prototype is enough + globals.set_proto(mc, object_class.prototype()); - globals.set_proto(mc, global_class.prototype()); - globals.set_vtable(mc, global_class.instance_vtable()); + // Create a new, full vtable for globals. + let global_obj_vtable = VTable::empty(mc); + global_obj_vtable.init_vtable( + global_classdef, + Some(object_class), + &global_classdef.traits(), + Some(scope), + Some(object_class.instance_vtable()), + mc, + ); + + globals.set_vtable(mc, global_obj_vtable); activation.context.avm2.toplevel_global_object = Some(globals); - script.set_global_class(mc, global_classdef); - script.set_global_class_obj(mc, global_class); + // Initialize the script + let script = Script::empty_script(mc, globals, domain); // From this point, `globals` is safe to be modified @@ -597,55 +685,44 @@ pub fn load_player_globals<'gc>( // After this point, it is safe to initialize any other classes. // Make sure to initialize superclasses *before* their subclasses! - avm2_system_class!(string, activation, string::create_class(activation), script); - avm2_system_class!( - boolean, - activation, - boolean::create_class(activation), - script - ); - avm2_system_class!(number, activation, number::create_class(activation), script); - avm2_system_class!(int, activation, int::create_class(activation), script); - avm2_system_class!(uint, activation, uint::create_class(activation), script); - avm2_system_class!( - namespace, - activation, - namespace::create_class(activation), - script - ); - avm2_system_class!(array, activation, array::create_class(activation), script); + avm2_system_class!(string, activation, string_class, script); + avm2_system_class!(boolean, activation, boolean_class, script); + avm2_system_class!(number, activation, number_class, script); + avm2_system_class!(int, activation, int_class, script); + avm2_system_class!(uint, activation, uint_class, script); + avm2_system_class!(namespace, activation, namespace_class, script); + avm2_system_class!(array, activation, array_class, script); - // void doesn't have a ClassObject - let void_def = void::create_class(activation); - activation.avm2().system_classes.as_mut().unwrap().void_def = void_def; - domain.export_class(void_def.name(), void_def, mc); - - avm2_system_class!( - generic_vector, - activation, - vector::create_generic_class(activation), - script - ); + avm2_system_class!(generic_vector, activation, vector_generic_class, script); vector_class( - Some(activation.avm2().classes().int), + Some(int_class), + vector_int_class, "Vector$int", script, activation, )?; vector_class( - Some(activation.avm2().classes().uint), + Some(uint_class), + vector_uint_class, "Vector$uint", script, activation, )?; vector_class( - Some(activation.avm2().classes().number), + Some(number_class), + vector_number_class, "Vector$double", script, activation, )?; - let object_vector = vector_class(None, "Vector$object", script, activation)?; + let object_vector = vector_class( + None, + vector_object_class, + "Vector$object", + script, + activation, + )?; activation .avm2() .system_classes @@ -653,7 +730,7 @@ pub fn load_player_globals<'gc>( .unwrap() .object_vector = object_vector; - avm2_system_class!(date, activation, date::create_class(activation), script); + avm2_system_class!(date, activation, date_class, script); // Inside this call, the macro `avm2_system_classes_playerglobal` // triggers classloading. Therefore, we run `load_playerglobal` @@ -661,22 +738,10 @@ pub fn load_player_globals<'gc>( // this call. load_playerglobal(activation, domain)?; - // Except for `trace`, top-level builtin functions are defined - // on the `global` object. - define_fn_on_global(activation, "", "decodeURI", script); - define_fn_on_global(activation, "", "decodeURIComponent", script); - define_fn_on_global(activation, "", "encodeURI", script); - define_fn_on_global(activation, "", "encodeURIComponent", script); - define_fn_on_global(activation, "", "escape", script); - define_fn_on_global(activation, "", "unescape", script); - define_fn_on_global(activation, "", "isXMLName", script); - define_fn_on_global(activation, "", "isFinite", script); - define_fn_on_global(activation, "", "isNaN", script); - define_fn_on_global(activation, "", "parseFloat", script); - define_fn_on_global(activation, "", "parseInt", script); - - global_classdef.mark_traits_loaded(mc); - global_classdef.init_vtable(activation.context)?; + for function_name in function_trait_list { + // Now copy those functions to the global object. + define_fn_on_global(activation, function_name, script); + } Ok(()) } @@ -731,7 +796,7 @@ fn load_playerglobal<'gc>( let _ = tag_utils::decode_tags(&mut reader, tag_callback); macro_rules! avm2_system_classes_playerglobal { - ($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { + ($activation:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => { let activation = $activation; $( // Lookup with the highest version, so we we see all defined classes here @@ -750,7 +815,6 @@ fn load_playerglobal<'gc>( // and are stored in 'avm2().system_classes' avm2_system_classes_playerglobal!( &mut *activation, - script, [ ("", "Error", error), ("", "ArgumentError", argumenterror), diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 119be5c61..77ca77666 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -1247,10 +1247,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "Array"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/avmplus.rs b/core/src/avm2/globals/avmplus.rs index 957cd7ca2..930ab2a8a 100644 --- a/core/src/avm2/globals/avmplus.rs +++ b/core/src/avm2/globals/avmplus.rs @@ -377,7 +377,7 @@ fn describe_internal_body<'gc>( let declared_by = method.class; if flags.contains(DescribeTypeFlags::HIDE_OBJECT) - && declared_by == activation.avm2().classes().object.inner_class_definition() + && declared_by == activation.avm2().class_defs().object { continue; } diff --git a/core/src/avm2/globals/boolean.rs b/core/src/avm2/globals/boolean.rs index 1fa744bd7..bdbab0a30 100644 --- a/core/src/avm2/globals/boolean.rs +++ b/core/src/avm2/globals/boolean.rs @@ -136,10 +136,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "Boolean"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/date.rs b/core/src/avm2/globals/date.rs index 9c8bd2bca..2bd786ef2 100644 --- a/core/src/avm2/globals/date.rs +++ b/core/src/avm2/globals/date.rs @@ -1326,10 +1326,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "Date"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/global_scope.rs b/core/src/avm2/globals/global_scope.rs index 103bc3acd..b4c0f1063 100644 --- a/core/src/avm2/globals/global_scope.rs +++ b/core/src/avm2/globals/global_scope.rs @@ -8,6 +8,7 @@ use crate::avm2::activation::Activation; use crate::avm2::class::Class; use crate::avm2::method::Method; use crate::avm2::object::Object; +use crate::avm2::traits::Trait; use crate::avm2::value::Value; use crate::avm2::Error; use crate::avm2::QName; @@ -21,42 +22,24 @@ pub fn instance_init<'gc>( Ok(Value::Undefined) } -/// Implements `global`'s class constructor. -pub fn class_init<'gc>( - _activation: &mut Activation<'_, 'gc>, - _this: Object<'gc>, - _args: &[Value<'gc>], -) -> Result, Error<'gc>> { - Ok(Value::Undefined) -} - /// Construct `global`'s class. pub fn create_class<'gc>( activation: &mut Activation<'_, 'gc>, - object_classdef: Class<'gc>, - class_classdef: Class<'gc>, + traits: Vec>, ) -> Class<'gc> { let mc = activation.context.gc_context; - let class = Class::new( + let class = Class::custom_new( QName::new(activation.avm2().public_namespace_base_version, "global"), - Some(object_classdef), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), - Method::from_builtin(class_init, "", mc), - class_classdef, mc, ); - class.mark_traits_loaded(activation.context.gc_context); + class.set_traits(mc, traits); + class.mark_traits_loaded(mc); class .init_vtable(activation.context) .expect("Native class's vtable should initialize"); - let c_class = class.c_class().expect("Class::new returns an i_class"); - - c_class.mark_traits_loaded(activation.context.gc_context); - c_class - .init_vtable(activation.context) - .expect("Native class's vtable should initialize"); - class } diff --git a/core/src/avm2/globals/int.rs b/core/src/avm2/globals/int.rs index a01100d0a..5847983ca 100644 --- a/core/src/avm2/globals/int.rs +++ b/core/src/avm2/globals/int.rs @@ -221,7 +221,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "int"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin_and_params( instance_init, "", @@ -235,7 +235,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { mc, ), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/namespace.rs b/core/src/avm2/globals/namespace.rs index 8a77ff0d2..73e54e9ad 100644 --- a/core/src/avm2/globals/namespace.rs +++ b/core/src/avm2/globals/namespace.rs @@ -174,10 +174,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "Namespace"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/number.rs b/core/src/avm2/globals/number.rs index 1b209bfd7..72fb67d4c 100644 --- a/core/src/avm2/globals/number.rs +++ b/core/src/avm2/globals/number.rs @@ -376,10 +376,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "Number"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index 323b8eaca..2f64725dc 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -689,10 +689,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "String"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/uint.rs b/core/src/avm2/globals/uint.rs index 8f5e33f2c..6e22186b8 100644 --- a/core/src/avm2/globals/uint.rs +++ b/core/src/avm2/globals/uint.rs @@ -222,7 +222,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "uint"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin_and_params( instance_init, "", @@ -236,7 +236,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { mc, ), Method::from_builtin(class_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index 3c6212a07..f3e63983c 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -901,10 +901,10 @@ pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class< let mc = activation.context.gc_context; let class = Class::new( QName::new(activation.avm2().vector_public_namespace, "Vector"), - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(generic_init, "", mc), Method::from_builtin(generic_init, "", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); @@ -947,10 +947,10 @@ pub fn create_builtin_class<'gc>( let class = Class::new( name, - Some(activation.avm2().classes().object.inner_class_definition()), + Some(activation.avm2().class_defs().object), Method::from_builtin(instance_init, " instance initializer>", mc), Method::from_builtin(class_init, " class initializer>", mc), - activation.avm2().classes().class.inner_class_definition(), + activation.avm2().class_defs().class, mc, ); diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index e29dd0f55..4df199f28 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -16,7 +16,6 @@ use crate::avm2::vtable::{ClassBoundMethod, VTable}; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; -use crate::avm2::QName; use crate::bitmap::bitmap_data::BitmapDataWrapper; use crate::display_object::DisplayObject; use crate::html::TextFormat; @@ -860,23 +859,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy base.install_bound_method(mc, disp_id, function) } - /// Install a const trait on the global object. - /// This should only ever be called on the `global` object, during initialization. - #[no_dynamic] - fn install_const_late( - &self, - mc: &Mutation<'gc>, - name: QName<'gc>, - value: Value<'gc>, - class: Class<'gc>, - ) { - let new_slot_id = self - .vtable() - .install_const_trait_late(mc, name, value, class); - - self.base().install_const_slot_late(mc, new_slot_id, value); - } - #[no_dynamic] fn install_instance_slots(&self, mc: &Mutation<'gc>) { self.base().install_instance_slots(mc); diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 5289321c1..8cfb220fe 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -95,7 +95,6 @@ impl<'gc> ScriptObject<'gc> { /// This should *not* be used unless you really need /// to do something low-level, weird or lazily initialize the object. /// You shouldn't let scripts observe this weirdness. - /// Another exception is ES3 class-less objects, which we don't really understand well :) /// /// The "everyday" way to create a normal empty ScriptObject (AS "Object") is to call /// `avm2.classes().object.construct(self, &[])`. @@ -355,19 +354,6 @@ impl<'gc> ScriptObjectWrapper<'gc> { } } - /// Set a slot by its index. This does extend the array if needed. - /// This should only be used during AVM initialization, not at runtime. - pub fn install_const_slot_late(&self, mc: &Mutation<'gc>, id: u32, value: Value<'gc>) { - let mut slots = self.slots_mut(mc); - - if slots.len() < id as usize + 1 { - slots.resize(id as usize + 1, Value::Undefined); - } - if let Some(slot) = slots.get_mut(id as usize) { - *slot = value; - } - } - /// Retrieve a bound method from the method table. pub fn get_bound_method(&self, id: u32) -> Option> { self.bound_methods().get(id as usize).and_then(|v| *v) diff --git a/core/src/avm2/optimize.rs b/core/src/avm2/optimize.rs index c556ea9eb..234dbf711 100644 --- a/core/src/avm2/optimize.rs +++ b/core/src/avm2/optimize.rs @@ -100,7 +100,7 @@ impl<'gc> OptValue<'gc> { || self.class == Some(classes.uint.inner_class_definition()) || self.class == Some(classes.number.inner_class_definition()) || self.class == Some(classes.boolean.inner_class_definition()) - || self.class == Some(classes.void_def) + || self.class == Some(activation.avm2().class_defs().void) } pub fn merged_with(self, other: OptValue<'gc>) -> OptValue<'gc> { @@ -344,7 +344,7 @@ pub fn optimize<'gc>( .classes() .function .inner_class_definition(), - void: activation.avm2().classes().void_def, + void: activation.avm2().class_defs().void, namespace: activation .avm2() .classes() diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index d45cfb590..090b28cfb 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -6,10 +6,11 @@ use crate::avm2::class::Class; use crate::avm2::domain::Domain; use crate::avm2::globals::global_scope; use crate::avm2::method::{BytecodeMethod, Method}; -use crate::avm2::object::{ClassObject, Object, TObject}; +use crate::avm2::object::{Object, ScriptObject, TObject}; use crate::avm2::scope::ScopeChain; use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::value::Value; +use crate::avm2::vtable::VTable; use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::{Avm2, Error}; @@ -18,7 +19,6 @@ use crate::string::{AvmAtom, AvmString}; use crate::tag_utils::SwfMovie; use crate::PlayerRuntime; use gc_arena::{Collect, Gc, GcCell, Mutation}; -use std::cell::Ref; use std::fmt::Debug; use std::rc::Rc; use std::sync::Arc; @@ -254,28 +254,7 @@ impl<'gc> TranslationUnit<'gc> { drop(read); - let object_class = activation.avm2().classes().object; - let class_classdef = activation.avm2().classes().class.inner_class_definition(); - - let global_classdef = global_scope::create_class( - activation, - object_class.inner_class_definition(), - class_classdef, - ); - - let global_class = - ClassObject::from_class(activation, global_classdef, Some(object_class))?; - - let global_obj = global_class.construct(activation, &[])?; - - let script = Script::from_abc_index( - self, - script_index, - global_obj, - global_class, - domain, - activation, - )?; + let script = Script::from_abc_index(self, script_index, domain, activation)?; self.0.write(activation.context.gc_context).scripts[script_index as usize] = Some(script); script.load_traits(self, script_index, activation)?; @@ -430,13 +409,7 @@ pub struct Script<'gc>(pub GcCell<'gc, ScriptData<'gc>>); #[collect(no_drop)] pub struct ScriptData<'gc> { /// The global object for the script. - globals: Object<'gc>, - - /// The class of this script's global object. - global_class: Option>, - - /// The ClassObject of this script's global object. - global_class_obj: Option>, + globals: Option>, /// The domain associated with this script. domain: Domain<'gc>, @@ -444,9 +417,6 @@ pub struct ScriptData<'gc> { /// The initializer method to run for the script. init: Method<'gc>, - /// Traits that this script uses. - traits: Vec>, - /// Whether or not we loaded our traits. traits_loaded: bool, @@ -474,16 +444,13 @@ impl<'gc> Script<'gc> { Self(GcCell::new( mc, ScriptData { - globals, - global_class: None, - global_class_obj: None, + globals: Some(globals), domain, init: Method::from_builtin( |_, _, _| Ok(Value::Undefined), "", mc, ), - traits: Vec::new(), traits_loaded: true, initialized: false, translation_unit: None, @@ -504,8 +471,6 @@ impl<'gc> Script<'gc> { pub fn from_abc_index( unit: TranslationUnit<'gc>, script_index: u32, - globals: Object<'gc>, - global_class_obj: ClassObject<'gc>, domain: Domain<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result> { @@ -521,12 +486,9 @@ impl<'gc> Script<'gc> { Ok(Self(GcCell::new( activation.context.gc_context, ScriptData { - globals, - global_class: Some(global_class_obj.inner_class_definition()), - global_class_obj: Some(global_class_obj), + globals: None, domain, init, - traits: Vec::new(), traits_loaded: false, initialized: false, translation_unit: Some(unit), @@ -547,7 +509,9 @@ impl<'gc> Script<'gc> { script_index: u32, activation: &mut Activation<'_, 'gc>, ) -> Result<(), Error<'gc>> { - let mut write = self.0.write(activation.context.gc_context); + let mc = activation.gc(); + + let mut write = self.0.write(mc); if write.traits_loaded { return Ok(()); @@ -561,30 +525,49 @@ impl<'gc> Script<'gc> { .get(script_index as usize) .ok_or_else(|| "LoadError: Script index not valid".into()); let script = script?; + let mut domain = write.domain; + + let mut traits = Vec::new(); for abc_trait in script.traits.iter() { let newtrait = Trait::from_abc_trait(unit, abc_trait, activation)?; - write - .domain - .export_definition(newtrait.name(), self, activation.context.gc_context); + domain.export_definition(newtrait.name(), self, mc); if let TraitKind::Class { class, .. } = newtrait.kind() { - write - .domain - .export_class(newtrait.name(), *class, activation.context.gc_context); + domain.export_class(newtrait.name(), *class, mc); } - write.traits.push(newtrait.clone()); - write - .global_class - .expect("Global class should be initialized") - .define_instance_trait(activation.context.gc_context, newtrait); + traits.push(newtrait); } drop(write); - self.global_class() - .mark_traits_loaded(activation.context.gc_context); - self.global_class().init_vtable(activation.context)?; + // Now that we have the traits, create the global class for this script + // and use it to initialize a vtable and global object. + + let global_class = global_scope::create_class(activation, traits); + + let scope = ScopeChain::new(domain); + let object_class = activation.avm2().classes().object; + + let global_obj_vtable = VTable::empty(mc); + global_obj_vtable.init_vtable( + global_class, + Some(object_class), + &global_class.traits(), + Some(scope), + Some(object_class.instance_vtable()), + mc, + ); + + let global_object = ScriptObject::custom_object( + mc, + global_class, + object_class.proto(), // Just use Object's prototype + global_obj_vtable, + ); + global_object.install_instance_slots(mc); + + self.0.write(mc).globals = Some(global_object); Ok(()) } @@ -592,7 +575,9 @@ impl<'gc> Script<'gc> { /// Return the entrypoint for the script and the scope it should run in. pub fn init(self) -> (Method<'gc>, Object<'gc>, Domain<'gc>) { let read = self.0.read(); - (read.init, read.globals, read.domain) + let globals = read.globals.expect("Global object should be initialized"); + + (read.init, globals, read.domain) } pub fn domain(self) -> Domain<'gc> { @@ -610,16 +595,9 @@ impl<'gc> Script<'gc> { pub fn global_class(self) -> Class<'gc> { self.0 .read() - .global_class - .expect("Global class should be initialized if it is accessed") - } - - pub fn set_global_class(self, mc: &Mutation<'gc>, global_class: Class<'gc>) { - self.0.write(mc).global_class = Some(global_class); - } - - pub fn set_global_class_obj(self, mc: &Mutation<'gc>, global_class_obj: ClassObject<'gc>) { - self.0.write(mc).global_class_obj = Some(global_class_obj); + .globals + .expect("Global object should be initialized") + .instance_class() } /// Return the global scope for the script. @@ -629,46 +607,16 @@ impl<'gc> Script<'gc> { pub fn globals(self, context: &mut UpdateContext<'gc>) -> Result, Error<'gc>> { let mut write = self.0.write(context.gc_context); + let globals = write.globals.expect("Global object should be initialized"); + if !write.initialized { write.initialized = true; - - let globals = write.globals; - let domain = write.domain; - drop(write); - let scope = ScopeChain::new(domain); - - globals.vtable().init_vtable( - globals.instance_class(), - Some(context.avm2.classes().object), - &self.traits()?, - Some(scope), - None, - context.gc_context, - ); - globals.install_instance_slots(context.gc_context); - Avm2::run_script_initializer(self, context)?; - - Ok(globals) - } else { - Ok(write.globals) - } - } - - /// Return traits for this script. - /// - /// This function will return an error if it is incorrectly called before - /// traits are loaded. - pub fn traits<'a>(&'a self) -> Result]>, Error<'gc>> { - let read = self.0.read(); - - if !read.traits_loaded { - return Err("LoadError: Script traits accessed before they were loaded!".into()); } - Ok(Ref::map(read, |read| &read.traits[..])) + Ok(globals) } } diff --git a/core/src/avm2/value.rs b/core/src/avm2/value.rs index 1cdbfa8b2..45ea2096c 100644 --- a/core/src/avm2/value.rs +++ b/core/src/avm2/value.rs @@ -1007,7 +1007,7 @@ impl<'gc> Value<'gc> { } if matches!(self, Value::Undefined) || matches!(self, Value::Null) { - if class == activation.avm2().classes().void_def { + if class == activation.avm2().class_defs().void { return Ok(Value::Undefined); } return Ok(Value::Null); @@ -1104,7 +1104,7 @@ impl<'gc> Value<'gc> { } if let Value::Undefined = self { - if type_object == activation.avm2().classes().void_def { + if type_object == activation.avm2().class_defs().void { return true; } } diff --git a/core/src/avm2/vector.rs b/core/src/avm2/vector.rs index 1ba8c99ee..56c694fcb 100644 --- a/core/src/avm2/vector.rs +++ b/core/src/avm2/vector.rs @@ -140,7 +140,7 @@ impl<'gc> VectorStorage<'gc> { /// Get the value type this vector coerces things to. pub fn value_type_for_coercion(&self, activation: &mut Activation<'_, 'gc>) -> Class<'gc> { self.value_type - .unwrap_or_else(|| activation.avm2().classes().object.inner_class_definition()) + .unwrap_or_else(|| activation.avm2().class_defs().object) } /// Check if a vector index is in bounds. diff --git a/core/src/avm2/vtable.rs b/core/src/avm2/vtable.rs index a91625ad6..76b5ed897 100644 --- a/core/src/avm2/vtable.rs +++ b/core/src/avm2/vtable.rs @@ -534,28 +534,6 @@ impl<'gc> VTable<'gc> { ) } - /// Install a const trait on the global object. - /// This should only ever be called via `Object::install_const_late`, - /// on the `global` object. - pub fn install_const_trait_late( - self, - mc: &Mutation<'gc>, - name: QName<'gc>, - value: Value<'gc>, - class: Class<'gc>, - ) -> u32 { - let mut write = self.0.write(mc); - - write.default_slots.push(Some(value)); - let new_slot_id = write.default_slots.len() as u32 - 1; - write - .resolved_traits - .insert(name, Property::new_const_slot(new_slot_id)); - write.slot_classes.push(PropertyClass::Class(class)); - - new_slot_id - } - /// Install an existing trait under a new name, provided by interface. /// This should only ever be called by `link_interfaces`. pub fn copy_property_for_interface(