diff --git a/core/src/avm2/class.rs b/core/src/avm2/class.rs index 2475f25d3..f6a118361 100644 --- a/core/src/avm2/class.rs +++ b/core/src/avm2/class.rs @@ -7,6 +7,7 @@ use crate::avm2::object::{ClassObject, Object}; use crate::avm2::script::TranslationUnit; use crate::avm2::traits::{Trait, TraitKind}; use crate::avm2::value::Value; +use crate::avm2::vtable::VTable; use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; @@ -17,9 +18,9 @@ use fnv::FnvHashMap; use gc_arena::{Collect, GcCell, Mutation}; use std::cell::Ref; +use std::collections::HashSet; use std::fmt; use std::hash::{Hash, Hasher}; -use std::ops::Deref; use swf::avm2::types::{ Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody, @@ -101,7 +102,7 @@ pub struct ClassData<'gc> { /// The list of interfaces this class directly implements. This does not include any /// superinterfaces, nor interfaces implemented by the superclass. - direct_interfaces: Vec>, + direct_interfaces: Vec>, /// The instance allocator for this class. /// @@ -137,6 +138,8 @@ pub struct ClassData<'gc> { /// properties that would match. instance_traits: Vec>, + instance_vtable: VTable<'gc>, + /// The class initializer for this class. /// /// Must be called once and only once prior to any use of this class. @@ -238,6 +241,7 @@ impl<'gc> Class<'gc> { instance_init, native_instance_init, instance_traits: Vec::new(), + instance_vtable: VTable::empty(mc), class_init, class_initializer_called: false, call_handler: None, @@ -302,6 +306,9 @@ impl<'gc> Class<'gc> { new_class.set_param(mc, Some(Some(param))); new_class.0.write(mc).call_handler = object_vector_cls.call_handler(); + new_class + .init_vtable(context) + .expect("Vector class doesn't have any interfaces, so `init_vtable` cannot error"); drop(this_read); @@ -383,10 +390,18 @@ impl<'gc> Class<'gc> { let mut interfaces = Vec::with_capacity(abc_instance.interfaces.len()); for interface_name in &abc_instance.interfaces { + let multiname = unit.pool_multiname_static(*interface_name, &mut activation.context)?; + interfaces.push( - unit.pool_multiname_static(*interface_name, &mut activation.context)? - .deref() - .clone(), + activation + .domain() + .get_class(&mut activation.context, &multiname) + .ok_or_else(|| { + make_error_1014( + activation, + multiname.to_qualified_name(activation.context.gc_context), + ) + })?, ); } @@ -453,6 +468,7 @@ impl<'gc> Class<'gc> { instance_init, native_instance_init, instance_traits: Vec::new(), + instance_vtable: VTable::empty(activation.context.gc_context), class_init, class_initializer_called: false, call_handler: native_call_handler, @@ -588,6 +604,79 @@ impl<'gc> Class<'gc> { Ok(()) } + pub fn init_vtable(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> { + let read = self.0.read(); + + if !read.traits_loaded { + panic!( + "Attempted to initialize vtable on a class that did not have its traits loaded yet" + ); + } + + read.instance_vtable.init_vtable( + None, + self.protected_namespace(), + &read.instance_traits, + None, + read.super_class.map(|c| c.instance_vtable()), + context, + ); + drop(read); + + self.link_interfaces(context)?; + + Ok(()) + } + + pub fn link_interfaces(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> { + let mut interfaces = Vec::with_capacity(self.direct_interfaces().len()); + + let mut dedup = HashSet::new(); + let mut queue = vec![self]; + while let Some(cls) = queue.pop() { + for interface in &*cls.direct_interfaces() { + if !interface.is_interface() { + return Err(format!( + "Class {:?} is not an interface and cannot be implemented by classes", + interface.name().local_name() + ) + .into()); + } + + if dedup.insert(ClassHashWrapper(*interface)) { + queue.push(*interface); + interfaces.push(*interface); + } + } + + if let Some(super_class) = cls.super_class() { + queue.push(super_class); + } + } + + // FIXME - we should only be copying properties for newly-implemented + // interfaces (i.e. those that were not already implemented by the superclass) + // Otherwise, our behavior diverges from Flash Player in certain cases. + // See the ignored test 'tests/tests/swfs/avm2/weird_superinterface_properties/' + for interface in interfaces { + for interface_trait in &*interface.instance_traits() { + if !interface_trait.name().namespace().is_public() { + let public_name = QName::new( + context.avm2.public_namespace_vm_internal, + interface_trait.name().local_name(), + ); + self.0.read().instance_vtable.copy_property_for_interface( + context.gc_context, + public_name, + interface_trait.name(), + ); + } + } + } + + Ok(()) + } + pub fn for_activation( activation: &mut Activation<'_, 'gc>, translation_unit: TranslationUnit<'gc>, @@ -606,7 +695,7 @@ impl<'gc> Class<'gc> { )?); } - Ok(Class(GcCell::new( + let class = Class(GcCell::new( activation.context.gc_context, ClassData { name: QName::new(activation.avm2().public_namespace_base_version, name), @@ -627,6 +716,7 @@ impl<'gc> Class<'gc> { activation.context.gc_context, ), instance_traits: traits, + instance_vtable: VTable::empty(activation.context.gc_context), class_init: Method::from_builtin( |_, _, _| Ok(Value::Undefined), "", @@ -640,7 +730,15 @@ impl<'gc> Class<'gc> { applications: Default::default(), class_objects: Vec::new(), }, - ))) + )); + + class.init_vtable(&mut activation.context)?; + + Ok(class) + } + + pub fn instance_vtable(self) -> VTable<'gc> { + self.0.read().instance_vtable } pub fn name(self) -> QName<'gc> { @@ -667,6 +765,42 @@ impl<'gc> Class<'gc> { self.0.read().protected_namespace } + pub fn mark_traits_loaded(self, mc: &Mutation<'gc>) { + self.0.write(mc).traits_loaded = true; + } + + #[inline(never)] + pub fn define_constant_class_instance_trait( + self, + activation: &mut Activation<'_, 'gc>, + name: QName<'gc>, + value: Value<'gc>, + ) { + self.define_instance_trait( + activation.context.gc_context, + Trait::from_const( + name, + Multiname::new(activation.avm2().public_namespace_base_version, "Class"), + Some(value), + ), + ); + } + #[inline(never)] + pub fn define_constant_function_instance_trait( + self, + activation: &mut Activation<'_, 'gc>, + name: QName<'gc>, + value: Value<'gc>, + ) { + self.define_instance_trait( + activation.context.gc_context, + Trait::from_const( + name, + Multiname::new(activation.avm2().public_namespace_base_version, "Function"), + Some(value), + ), + ); + } #[inline(never)] pub fn define_constant_number_class_traits( self, @@ -941,14 +1075,10 @@ impl<'gc> Class<'gc> { self.0.write(mc).class_initializer_called = true; } - pub fn direct_interfaces(&self) -> Ref>> { + pub fn direct_interfaces(&self) -> Ref>> { Ref::map(self.0.read(), |c| &c.direct_interfaces) } - pub fn implements(self, mc: &Mutation<'gc>, iface: Multiname<'gc>) { - self.0.write(mc).direct_interfaces.push(iface) - } - /// Determine if this class is sealed (no dynamic properties) pub fn is_sealed(self) -> bool { self.0.read().attributes.contains(ClassAttributes::SEALED) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index efc33acfb..8b1eac154 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -11,7 +11,7 @@ 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, Mutation}; +use gc_arena::Collect; use std::sync::Arc; use swf::TagCode; @@ -335,6 +335,9 @@ fn define_fn_on_global<'gc>( func, activation.avm2().classes().function, ); + script + .global_class() + .define_constant_function_instance_trait(activation, qname, func); } /// Add a fully-formed class object builtin to the global scope. @@ -342,7 +345,7 @@ fn define_fn_on_global<'gc>( /// This allows the caller to pre-populate the class's prototype with dynamic /// properties, if necessary. fn dynamic_class<'gc>( - mc: &Mutation<'gc>, + activation: &mut Activation<'_, 'gc>, class_object: ClassObject<'gc>, script: Script<'gc>, // The `ClassObject` of the `Class` class @@ -352,8 +355,18 @@ fn dynamic_class<'gc>( let class = class_object.inner_class_definition(); let name = class.name(); - global.install_const_late(mc, name, class_object.into(), class_class); - domain.export_definition(name, script, mc) + global.install_const_late( + activation.context.gc_context, + name, + class_object.into(), + class_class, + ); + script.global_class().define_constant_class_instance_trait( + activation, + name, + class_object.into(), + ); + domain.export_definition(name, script, activation.context.gc_context) } /// Add a class builtin to the global scope. @@ -387,6 +400,11 @@ fn class<'gc>( class_object.into(), activation.avm2().classes().class, ); + script.global_class().define_constant_class_instance_trait( + activation, + class_name, + class_object.into(), + ); domain.export_definition(class_name, script, mc); domain.export_class(class_name, class_def, mc); Ok(class_object) @@ -421,6 +439,11 @@ fn vector_class<'gc>( vector_cls.into(), activation.avm2().classes().class, ); + script.global_class().define_constant_class_instance_trait( + activation, + legacy_name, + vector_cls.into(), + ); domain.export_definition(legacy_name, script, mc); Ok(vector_cls) } @@ -531,18 +554,20 @@ pub fn load_player_globals<'gc>( let class_class = class_class.into_finished_class(activation)?; let object_class = object_class.into_finished_class(activation)?; let fn_class = fn_class.into_finished_class(activation)?; - let _global_class = global_class.into_finished_class(activation)?; + let global_class = global_class.into_finished_class(activation)?; globals.set_proto(mc, global_proto); globals.set_instance_of(mc, global_class); activation.context.avm2.toplevel_global_object = Some(globals); + script.set_global_class(mc, global_classdef); + // From this point, `globals` is safe to be modified - dynamic_class(mc, object_class, script, class_class); - dynamic_class(mc, fn_class, script, class_class); - dynamic_class(mc, class_class, script, class_class); + dynamic_class(activation, object_class, script, class_class); + dynamic_class(activation, fn_class, script, class_class); + dynamic_class(activation, class_class, script, class_class); // After this point, it is safe to initialize any other classes. // Make sure to initialize superclasses *before* their subclasses! @@ -624,6 +649,9 @@ pub fn load_player_globals<'gc>( define_fn_on_global(activation, "", "parseFloat", script); define_fn_on_global(activation, "", "parseInt", script); + global_classdef.mark_traits_loaded(mc); + global_classdef.init_vtable(&mut activation.context)?; + Ok(()) } diff --git a/core/src/avm2/globals/array.rs b/core/src/avm2/globals/array.rs index 49f125956..ca93c31a3 100644 --- a/core/src/avm2/globals/array.rs +++ b/core/src/avm2/globals/array.rs @@ -1300,5 +1300,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { activation, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/avmplus.rs b/core/src/avm2/globals/avmplus.rs index f68124ed2..d2edc8d58 100644 --- a/core/src/avm2/globals/avmplus.rs +++ b/core/src/avm2/globals/avmplus.rs @@ -383,12 +383,13 @@ fn describe_internal_body<'gc>( let declared_by = method.class; if flags.contains(DescribeTypeFlags::HIDE_OBJECT) - && declared_by == activation.avm2().classes().object + && declared_by == Some(activation.avm2().classes().object) { continue; } let declared_by_name = declared_by + .unwrap() .inner_class_definition() .name() .to_qualified_name(activation.context.gc_context); @@ -476,6 +477,7 @@ fn describe_internal_body<'gc>( let accessor_type = method_type.to_qualified_name_or_star(activation.context.gc_context); let declared_by = defining_class + .unwrap() .inner_class_definition() .name() .to_qualified_name(activation.context.gc_context); diff --git a/core/src/avm2/globals/boolean.rs b/core/src/avm2/globals/boolean.rs index 7fef532a8..01014f41e 100644 --- a/core/src/avm2/globals/boolean.rs +++ b/core/src/avm2/globals/boolean.rs @@ -170,5 +170,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { activation, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/class.rs b/core/src/avm2/globals/class.rs index 9edc96ece..d10f174e5 100644 --- a/core/src/avm2/globals/class.rs +++ b/core/src/avm2/globals/class.rs @@ -75,5 +75,10 @@ pub fn create_class<'gc>( PUBLIC_INSTANCE_PROPERTIES, ); + class_class.mark_traits_loaded(activation.context.gc_context); + class_class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class_class } diff --git a/core/src/avm2/globals/date.rs b/core/src/avm2/globals/date.rs index 78c5e18af..9b36ed9ca 100644 --- a/core/src/avm2/globals/date.rs +++ b/core/src/avm2/globals/date.rs @@ -1392,5 +1392,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { activation, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/function.rs b/core/src/avm2/globals/function.rs index cec6c1822..de4bf8c76 100644 --- a/core/src/avm2/globals/function.rs +++ b/core/src/avm2/globals/function.rs @@ -268,5 +268,10 @@ pub fn create_class<'gc>( Method::from_builtin(class_call, "", gc_context), ); + function_class.mark_traits_loaded(activation.context.gc_context); + function_class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + function_class } diff --git a/core/src/avm2/globals/global_scope.rs b/core/src/avm2/globals/global_scope.rs index e4ff9ca83..8059d4a43 100644 --- a/core/src/avm2/globals/global_scope.rs +++ b/core/src/avm2/globals/global_scope.rs @@ -36,11 +36,18 @@ pub fn create_class<'gc>( object_class: Class<'gc>, ) -> Class<'gc> { let mc = activation.context.gc_context; - Class::new( + let class = Class::new( QName::new(activation.avm2().public_namespace_base_version, "global"), Some(object_class), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), mc, - ) + ); + + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut 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 003db0aef..f2c7af567 100644 --- a/core/src/avm2/globals/int.rs +++ b/core/src/avm2/globals/int.rs @@ -273,5 +273,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { AS3_INSTANCE_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/namespace.rs b/core/src/avm2/globals/namespace.rs index 831c41c7f..8544e7f1c 100644 --- a/core/src/avm2/globals/namespace.rs +++ b/core/src/avm2/globals/namespace.rs @@ -217,5 +217,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { PUBLIC_INSTANCE_AND_PROTO_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/number.rs b/core/src/avm2/globals/number.rs index c6a22aeac..e0653d501 100644 --- a/core/src/avm2/globals/number.rs +++ b/core/src/avm2/globals/number.rs @@ -432,5 +432,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { AS3_INSTANCE_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/object.rs b/core/src/avm2/globals/object.rs index a8fe819b2..b84bb5fe5 100644 --- a/core/src/avm2/globals/object.rs +++ b/core/src/avm2/globals/object.rs @@ -321,5 +321,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { INTERNAL_INIT_METHOD, ); + object_class.mark_traits_loaded(activation.context.gc_context); + object_class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + object_class } diff --git a/core/src/avm2/globals/string.rs b/core/src/avm2/globals/string.rs index 1b00824c6..069c48701 100644 --- a/core/src/avm2/globals/string.rs +++ b/core/src/avm2/globals/string.rs @@ -750,6 +750,11 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { PUBLIC_CLASS_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/uint.rs b/core/src/avm2/globals/uint.rs index e0f0051bb..d72fe0a63 100644 --- a/core/src/avm2/globals/uint.rs +++ b/core/src/avm2/globals/uint.rs @@ -276,5 +276,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { AS3_INSTANCE_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/vector.rs b/core/src/avm2/globals/vector.rs index afc9a42cf..466dcb0b5 100644 --- a/core/src/avm2/globals/vector.rs +++ b/core/src/avm2/globals/vector.rs @@ -922,6 +922,12 @@ pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class< class.set_attributes(mc, ClassAttributes::GENERIC | ClassAttributes::FINAL); class.set_instance_allocator(mc, generic_vector_allocator); + + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } @@ -1007,5 +1013,10 @@ pub fn create_builtin_class<'gc>( AS3_INSTANCE_METHODS, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/globals/void.rs b/core/src/avm2/globals/void.rs index 5a346b683..820914d87 100644 --- a/core/src/avm2/globals/void.rs +++ b/core/src/avm2/globals/void.rs @@ -29,5 +29,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> { mc, ); + class.mark_traits_loaded(activation.context.gc_context); + class + .init_vtable(&mut activation.context) + .expect("Native class's vtable should initialize"); + class } diff --git a/core/src/avm2/object.rs b/core/src/avm2/object.rs index b88767541..ebf8e5d9a 100644 --- a/core/src/avm2/object.rs +++ b/core/src/avm2/object.rs @@ -602,12 +602,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy return exec( method, - scope, + scope.expect("Scope should exist here"), self.into(), - Some(class), + class, arguments, activation, - class.into(), //Callee deliberately invalid. + ScriptObject::custom_object(activation.context.gc_context, None, None), // Callee deliberately invalid. ); } diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index 233db2135..baf2a55a9 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -248,12 +248,13 @@ impl<'gc> ClassObject<'gc> { class.validate_class(self.superclass_object())?; self.instance_vtable().init_vtable( - self, + Some(self), + class.protected_namespace(), &class.instance_traits(), - self.instance_scope(), + Some(self.instance_scope()), self.superclass_object().map(|cls| cls.instance_vtable()), - activation, - )?; + &mut activation.context, + ); Ok(()) } @@ -283,12 +284,13 @@ impl<'gc> ClassObject<'gc> { // class vtable == class traits + Class instance traits self.class_vtable().init_vtable( - self, + Some(self), + class.protected_namespace(), &class.class_traits(), - self.class_scope(), + Some(self.class_scope()), Some(self.instance_of().unwrap().instance_vtable()), - activation, - )?; + &mut activation.context, + ); self.link_interfaces(activation)?; self.install_class_vtable_and_slots(activation.context.gc_context); @@ -327,22 +329,13 @@ impl<'gc> ClassObject<'gc> { pub fn link_interfaces(self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> { let mut write = self.0.write(activation.context.gc_context); let class = write.class; - let scope = write.class_scope; - let interface_names = class.direct_interfaces().to_vec(); - let mut interfaces = Vec::with_capacity(interface_names.len()); + let mut interfaces = Vec::with_capacity(class.direct_interfaces().len()); let mut dedup = HashSet::new(); let mut queue = vec![class]; while let Some(cls) = queue.pop() { - for interface_name in &*cls.direct_interfaces() { - let interface = scope - .domain() - .get_class(&mut activation.context, interface_name) - .ok_or_else(|| { - Error::from(format!("Could not resolve interface {interface_name:?}")) - })?; - + for interface in &*cls.direct_interfaces() { if !interface.is_interface() { return Err(format!( "Class {:?} is not an interface and cannot be implemented by classes", @@ -351,9 +344,9 @@ impl<'gc> ClassObject<'gc> { .into()); } - if dedup.insert(ClassHashWrapper(interface)) { - queue.push(interface); - interfaces.push(interface); + if dedup.insert(ClassHashWrapper(*interface)) { + queue.push(*interface); + interfaces.push(*interface); } } @@ -569,8 +562,13 @@ impl<'gc> ClassObject<'gc> { scope, method, } = self.instance_vtable().get_full_method(disp_id).unwrap(); - let callee = - FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class)); + let callee = FunctionObject::from_method( + activation, + method, + scope.expect("Scope should exist here"), + Some(receiver), + class, + ); callee.call(receiver.into(), arguments, activation) } else { @@ -626,9 +624,9 @@ impl<'gc> ClassObject<'gc> { let callee = FunctionObject::from_method( activation, method, - scope, + scope.expect("Scope should exist here"), Some(receiver), - Some(class), + class, ); // We call getters, but return the actual function object for normal methods @@ -706,7 +704,7 @@ impl<'gc> ClassObject<'gc> { method, } = self.instance_vtable().get_full_method(disp_id).unwrap(); let callee = - FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class)); + FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), class); callee.call(receiver.into(), &[value], activation)?; Ok(()) diff --git a/core/src/avm2/optimize.rs b/core/src/avm2/optimize.rs index a239fcd90..2a5540acc 100644 --- a/core/src/avm2/optimize.rs +++ b/core/src/avm2/optimize.rs @@ -6,11 +6,12 @@ use crate::avm2::object::{ClassObject, TObject}; use crate::avm2::op::Op; use crate::avm2::property::Property; use crate::avm2::verify::JumpSources; +use crate::avm2::vtable::VTable; use gc_arena::Gc; use std::collections::HashMap; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy)] struct OptValue<'gc> { // This corresponds to the compile-time assumptions about the type: // - primitive types can't be undefined or null, @@ -21,7 +22,9 @@ struct OptValue<'gc> { // (say, a Value::Number above hardcoded int-range that's still representable as i32). // Note that `null is Object` is still `false`. So think of this type more in terms of // "could this value be a possible value of `var t: T`" - pub class: Option>, + pub class: Option>, + + pub vtable: Option>, // true if the value is guaranteed to be Value::Integer // should only be set if class is numeric. @@ -39,33 +42,59 @@ impl<'gc> OptValue<'gc> { pub fn any() -> Self { Self { class: None, + vtable: None, contains_valid_integer: false, contains_valid_unsigned: false, guaranteed_null: false, } } + pub fn null() -> Self { Self { class: None, + vtable: None, guaranteed_null: true, ..Self::any() } } + pub fn of_type(class: ClassObject<'gc>) -> Self { + let class = class.inner_class_definition(); Self { class: Some(class), + vtable: Some(class.instance_vtable()), ..Self::any() } } + pub fn of_type_from_class(class: Class<'gc>) -> Self { - // FIXME: Getting the ClassObject this way should be unnecessary - // after the ClassObject refactor - if let Some(cls) = class.class_object() { - Self::of_type(cls) - } else { - Self::any() + Self { + class: Some(class), + vtable: Some(class.instance_vtable()), + ..Self::any() } } + + pub fn vtable(self) -> Option> { + if let Some(class) = self.class { + if class.is_interface() { + return None; + } + } + + self.vtable + } +} + +impl<'gc> std::fmt::Debug for OptValue<'gc> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.debug_struct("OptValue") + .field("class", &self.class) + .field("contains_valid_integer", &self.contains_valid_integer) + .field("contains_valid_unsigned", &self.contains_valid_unsigned) + .field("guaranteed_null", &self.guaranteed_null) + .finish() + } } #[derive(Clone, Debug)] @@ -196,14 +225,32 @@ pub fn optimize<'gc>( // but this works since it's guaranteed to be set in `Activation::from_method`. let this_value = activation.local_register(0); - let this_class = if let Some(this_class) = activation.subclass_object() { + let (this_class, this_vtable) = if let Some(this_class) = activation.subclass_object() { if this_value.is_of_type(activation, this_class.inner_class_definition()) { - Some(this_class) + (Some(this_class), Some(this_class.inner_class_definition().instance_vtable())) + } else if this_value + .as_object() + .and_then(|o| o.as_class_object()) + .map(|c| c.inner_class_definition() == this_class.inner_class_definition()) + .unwrap_or(false) { + // Static method + ( + Some(activation.avm2().classes().class), + Some(this_class.class_vtable()), + ) } else { - None + (None, None) } } else { - None + (None, None) + }; + + let this_value = OptValue { + class: this_class.map(|c| c.inner_class_definition()), + vtable: this_vtable, + contains_valid_integer: false, + contains_valid_unsigned: false, + guaranteed_null: false, }; let argument_types = resolved_parameters @@ -213,9 +260,7 @@ pub fn optimize<'gc>( // Initial set of local types let mut initial_local_types = Locals::new(method_body.num_locals as usize); - if let Some(this_class) = this_class { - initial_local_types.set(0, OptValue::of_type(this_class)); - } + initial_local_types.set(0, this_value); for (i, argument_type) in argument_types.iter().enumerate() { if let Some(argument_type) = argument_type { @@ -303,10 +348,11 @@ pub fn optimize<'gc>( if let (Some(source_local_class), Some(target_local_class)) = (source_local.class, target_local.class) { - if source_local_class.inner_class_definition() - == target_local_class.inner_class_definition() - { - merged_types.set(i, OptValue::of_type(source_local_class)); + if source_local_class == target_local_class { + merged_types.set( + i, + OptValue::of_type_from_class(source_local_class), + ); } } } @@ -337,16 +383,16 @@ pub fn optimize<'gc>( } Op::CoerceB => { let stack_value = stack.pop_or_any(); - if stack_value.class == Some(types.boolean) { + if stack_value.class == Some(types.boolean.inner_class_definition()) { *op = Op::Nop; } stack.push_class_object(types.boolean); } Op::CoerceD => { let stack_value = stack.pop_or_any(); - if stack_value.class == Some(types.number) - || stack_value.class == Some(types.int) - || stack_value.class == Some(types.uint) + if stack_value.class == Some(types.number.inner_class_definition()) + || stack_value.class == Some(types.int.inner_class_definition()) + || stack_value.class == Some(types.uint.inner_class_definition()) { *op = Op::Nop; } @@ -508,12 +554,12 @@ pub fn optimize<'gc>( Op::Add => { let value2 = stack.pop_or_any(); let value1 = stack.pop_or_any(); - if (value1.class == Some(types.int) - || value1.class == Some(types.uint) - || value1.class == Some(types.number)) - && (value2.class == Some(types.int) - || value2.class == Some(types.uint) - || value2.class == Some(types.number)) + if (value1.class == Some(types.int.inner_class_definition()) + || value1.class == Some(types.uint.inner_class_definition()) + || value1.class == Some(types.number.inner_class_definition())) + && (value2.class == Some(types.int.inner_class_definition()) + || value2.class == Some(types.uint.inner_class_definition()) + || value2.class == Some(types.number.inner_class_definition())) { stack.push_class_object(types.number); } else { @@ -668,8 +714,8 @@ pub fn optimize<'gc>( // if T is non-nullable, we can assume the result is typed T new_value = OptValue::of_type_from_class(*class); } - if let Some(class_object) = stack_value.class { - if *class == class_object.inner_class_definition() { + if let Some(stack_class) = stack_value.class { + if *class == stack_class { // If type check is guaranteed, preserve original type // TODO: there are more cases when this can succeed, // like inheritance and numbers (`x: Number = 1; x as int;`) @@ -696,9 +742,9 @@ pub fn optimize<'gc>( { *op = Op::Nop; } - } else if let Some(class_object) = stack_value.class { + } else if let Some(stack_class) = stack_value.class { // TODO: this could check for inheritance - if *class == class_object.inner_class_definition() { + if *class == stack_class { *op = Op::Nop; } } @@ -760,12 +806,12 @@ pub fn optimize<'gc>( if !multiname.has_lazy_component() && has_simple_scoping { let outer_scope = activation.outer(); if !outer_scope.is_empty() { - if let Some(this_class) = this_class { - if this_class.instance_vtable().has_trait(&multiname) { + if let Some(this_vtable) = this_vtable { + if this_vtable.has_trait(&multiname) { *op = Op::GetScopeObject { index: 0 }; stack_push_done = true; - stack.push_class_object(this_class); + stack.push(this_value); } } else { stack_push_done = true; @@ -859,10 +905,10 @@ pub fn optimize<'gc>( let mut stack_push_done = false; let stack_value = stack.pop_or_any(); - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - let mut value_class = - class.instance_vtable().slot_classes()[*slot_id as usize]; + if let Some(vtable) = stack_value.vtable() { + let slot_classes = vtable.slot_classes(); + let value_class = slot_classes.get(*slot_id as usize).copied(); + if let Some(mut value_class) = value_class { let resolved_value_class = value_class.get_class(activation); if let Ok(class) = resolved_value_class { stack_push_done = true; @@ -874,7 +920,8 @@ pub fn optimize<'gc>( } } - class.instance_vtable().set_slot_class( + drop(slot_classes); + vtable.set_slot_class( activation.context.gc_context, *slot_id as usize, value_class, @@ -896,43 +943,40 @@ pub fn optimize<'gc>( let stack_value = stack.pop_or_any(); if !multiname.has_lazy_component() { - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - match class.instance_vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) - | Some(Property::ConstSlot { slot_id }) => { - *op = Op::GetSlot { index: slot_id }; + if let Some(vtable) = stack_value.vtable() { + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) + | Some(Property::ConstSlot { slot_id }) => { + *op = Op::GetSlot { index: slot_id }; - let mut value_class = - class.instance_vtable().slot_classes()[slot_id as usize]; - let resolved_value_class = value_class.get_class(activation); - if let Ok(class) = resolved_value_class { - stack_push_done = true; + let mut value_class = vtable.slot_classes()[slot_id as usize]; + let resolved_value_class = value_class.get_class(activation); + if let Ok(class) = resolved_value_class { + stack_push_done = true; - if let Some(class) = class { - stack.push_class(class); - } else { - stack.push_any(); - } + if let Some(class) = class { + stack.push_class(class); + } else { + stack.push_any(); } + } - class.instance_vtable().set_slot_class( - activation.context.gc_context, - slot_id as usize, - value_class, - ); - } - Some(Property::Virtual { - get: Some(disp_id), .. - }) => { - *op = Op::CallMethod { - num_args: 0, - index: disp_id, - push_return_value: true, - }; - } - _ => {} + vtable.set_slot_class( + activation.context.gc_context, + slot_id as usize, + value_class, + ); } + Some(Property::Virtual { + get: Some(disp_id), .. + }) => { + *op = Op::CallMethod { + num_args: 0, + index: disp_id, + push_return_value: true, + }; + } + _ => {} } } } @@ -948,45 +992,40 @@ pub fn optimize<'gc>( stack.pop_for_multiname(*multiname); let stack_value = stack.pop_or_any(); if !multiname.has_lazy_component() { - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - match class.instance_vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) - | Some(Property::ConstSlot { slot_id }) => { - *op = Op::SetSlot { index: slot_id }; + if let Some(vtable) = stack_value.vtable() { + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) + | Some(Property::ConstSlot { slot_id }) => { + *op = Op::SetSlot { index: slot_id }; - // If the set value's type is the same as the type of the slot, - // a SetSlotNoCoerce can be emitted. - let mut value_class = - class.instance_vtable().slot_classes()[slot_id as usize]; - let resolved_value_class = value_class.get_class(activation); + // If the set value's type is the same as the type of the slot, + // a SetSlotNoCoerce can be emitted. + let mut value_class = vtable.slot_classes()[slot_id as usize]; + let resolved_value_class = value_class.get_class(activation); - if let Ok(slot_class) = resolved_value_class { - if let Some(slot_class) = slot_class { - if let Some(set_value_class) = set_value.class { - if set_value_class.inner_class_definition() - == slot_class - { - *op = Op::SetSlotNoCoerce { index: slot_id }; - } + if let Ok(slot_class) = resolved_value_class { + if let Some(slot_class) = slot_class { + if let Some(set_value_class) = set_value.class { + if set_value_class == slot_class { + *op = Op::SetSlotNoCoerce { index: slot_id }; } - } else { - // Slot type was Any, no coercion will be done anyways - *op = Op::SetSlotNoCoerce { index: slot_id }; } + } else { + // Slot type was Any, no coercion will be done anyways + *op = Op::SetSlotNoCoerce { index: slot_id }; } } - Some(Property::Virtual { - set: Some(disp_id), .. - }) => { - *op = Op::CallMethod { - num_args: 1, - index: disp_id, - push_return_value: false, - }; - } - _ => {} } + Some(Property::Virtual { + set: Some(disp_id), .. + }) => { + *op = Op::CallMethod { + num_args: 1, + index: disp_id, + push_return_value: false, + }; + } + _ => {} } } } @@ -998,44 +1037,39 @@ pub fn optimize<'gc>( stack.pop_for_multiname(*multiname); let stack_value = stack.pop_or_any(); if !multiname.has_lazy_component() { - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - match class.instance_vtable().get_trait(multiname) { - Some(Property::Slot { slot_id }) => { - *op = Op::SetSlot { index: slot_id }; + if let Some(vtable) = stack_value.vtable() { + match vtable.get_trait(multiname) { + Some(Property::Slot { slot_id }) => { + *op = Op::SetSlot { index: slot_id }; - // If the set value's type is the same as the type of the slot, - // a SetSlotNoCoerce can be emitted. - let mut value_class = - class.instance_vtable().slot_classes()[slot_id as usize]; - let resolved_value_class = value_class.get_class(activation); + // If the set value's type is the same as the type of the slot, + // a SetSlotNoCoerce can be emitted. + let mut value_class = vtable.slot_classes()[slot_id as usize]; + let resolved_value_class = value_class.get_class(activation); - if let Ok(slot_class) = resolved_value_class { - if let Some(slot_class) = slot_class { - if let Some(set_value_class) = set_value.class { - if set_value_class.inner_class_definition() - == slot_class - { - *op = Op::SetSlotNoCoerce { index: slot_id }; - } + if let Ok(slot_class) = resolved_value_class { + if let Some(slot_class) = slot_class { + if let Some(set_value_class) = set_value.class { + if set_value_class == slot_class { + *op = Op::SetSlotNoCoerce { index: slot_id }; } - } else { - // Slot type was Any, no coercion will be done anyways - *op = Op::SetSlotNoCoerce { index: slot_id }; } + } else { + // Slot type was Any, no coercion will be done anyways + *op = Op::SetSlotNoCoerce { index: slot_id }; } } - Some(Property::Virtual { - set: Some(disp_id), .. - }) => { - *op = Op::CallMethod { - num_args: 1, - index: disp_id, - push_return_value: false, - }; - } - _ => {} } + Some(Property::Virtual { + set: Some(disp_id), .. + }) => { + *op = Op::CallMethod { + num_args: 1, + index: disp_id, + push_return_value: false, + }; + } + _ => {} } } } @@ -1128,18 +1162,16 @@ pub fn optimize<'gc>( let stack_value = stack.pop_or_any(); if !multiname.has_lazy_component() { - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - match class.instance_vtable().get_trait(multiname) { - Some(Property::Method { disp_id }) => { - *op = Op::CallMethod { - num_args: *num_args, - index: disp_id, - push_return_value: true, - }; - } - _ => {} + if let Some(vtable) = stack_value.vtable() { + match vtable.get_trait(multiname) { + Some(Property::Method { disp_id }) => { + *op = Op::CallMethod { + num_args: *num_args, + index: disp_id, + push_return_value: true, + }; } + _ => {} } } } @@ -1161,18 +1193,16 @@ pub fn optimize<'gc>( let stack_value = stack.pop_or_any(); if !multiname.has_lazy_component() { - if let Some(class) = stack_value.class { - if !class.inner_class_definition().is_interface() { - match class.instance_vtable().get_trait(multiname) { - Some(Property::Method { disp_id }) => { - *op = Op::CallMethod { - num_args: *num_args, - index: disp_id, - push_return_value: false, - }; - } - _ => {} + if let Some(vtable) = stack_value.vtable() { + match vtable.get_trait(multiname) { + Some(Property::Method { disp_id }) => { + *op = Op::CallMethod { + num_args: *num_args, + index: disp_id, + push_return_value: false, + }; } + _ => {} } } } @@ -1246,26 +1276,24 @@ pub fn optimize<'gc>( let global_scope = outer_scope.get_unchecked(0); if let Some(class) = global_scope.values().instance_of() { - if !class.inner_class_definition().is_interface() { - let mut value_class = - class.instance_vtable().slot_classes()[*slot_id as usize]; - let resolved_value_class = value_class.get_class(activation); - if let Ok(class) = resolved_value_class { - stack_push_done = true; + let mut value_class = + class.instance_vtable().slot_classes()[*slot_id as usize]; + let resolved_value_class = value_class.get_class(activation); + if let Ok(class) = resolved_value_class { + stack_push_done = true; - if let Some(class) = class { - stack.push_class(class); - } else { - stack.push_any(); - } + if let Some(class) = class { + stack.push_class(class); + } else { + stack.push_any(); } - - class.instance_vtable().set_slot_class( - activation.context.gc_context, - *slot_id as usize, - value_class, - ); } + + class.instance_vtable().set_slot_class( + activation.context.gc_context, + *slot_id as usize, + value_class, + ); } } @@ -1348,7 +1376,7 @@ pub fn optimize<'gc>( if let Some(return_type) = return_type { if let Some(stack_value_class) = stack_value.class { - if stack_value_class.inner_class_definition() == return_type { + if stack_value_class == return_type { *op = Op::ReturnValueNoCoerce; } } diff --git a/core/src/avm2/script.rs b/core/src/avm2/script.rs index fc19523d6..9c274ba84 100644 --- a/core/src/avm2/script.rs +++ b/core/src/avm2/script.rs @@ -229,6 +229,7 @@ impl<'gc> TranslationUnit<'gc> { self.0.write(activation.context.gc_context).classes[class_index as usize] = Some(class); class.load_traits(activation, self, class_index)?; + class.init_vtable(&mut activation.context)?; Ok(class) } @@ -258,8 +259,14 @@ impl<'gc> TranslationUnit<'gc> { let global_obj = global_class.construct(activation, &[])?; - let mut script = - Script::from_abc_index(self, script_index, global_obj, domain, activation)?; + let mut script = Script::from_abc_index( + self, + script_index, + global_obj, + global_classdef, + domain, + activation, + )?; self.0.write(activation.context.gc_context).scripts[script_index as usize] = Some(script); script.load_traits(self, script_index, activation)?; @@ -416,6 +423,9 @@ 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 domain associated with this script. domain: Domain<'gc>, @@ -451,6 +461,7 @@ impl<'gc> Script<'gc> { mc, ScriptData { globals, + global_class: None, domain, init: Method::from_builtin( |_, _, _| Ok(Value::Undefined), @@ -478,6 +489,7 @@ impl<'gc> Script<'gc> { unit: TranslationUnit<'gc>, script_index: u32, globals: Object<'gc>, + global_class: Class<'gc>, domain: Domain<'gc>, activation: &mut Activation<'_, 'gc>, ) -> Result> { @@ -494,6 +506,7 @@ impl<'gc> Script<'gc> { activation.context.gc_context, ScriptData { globals, + global_class: Some(global_class), domain, init, traits: Vec::new(), @@ -542,9 +555,19 @@ impl<'gc> Script<'gc> { .export_class(newtrait.name(), *class, activation.context.gc_context); } - write.traits.push(newtrait); + write.traits.push(newtrait.clone()); + write + .global_class + .expect("Global class should be initialized") + .define_instance_trait(activation.context.gc_context, newtrait); } + drop(write); + + self.global_class() + .mark_traits_loaded(activation.context.gc_context); + self.global_class().init_vtable(&mut activation.context)?; + Ok(()) } @@ -562,6 +585,17 @@ impl<'gc> Script<'gc> { self.0.read().translation_unit } + 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); + } + /// Return the global scope for the script. /// /// If the script has not yet been initialized, this will initialize it on @@ -581,12 +615,17 @@ impl<'gc> Script<'gc> { let scope = ScopeChain::new(domain); globals.vtable().unwrap().init_vtable( - globals.instance_of().unwrap(), + globals.instance_of(), + globals + .instance_of() + .unwrap() + .inner_class_definition() + .protected_namespace(), &self.traits()?, - scope, + Some(scope), None, - &mut null_activation, - )?; + &mut null_activation.context, + ); globals.install_instance_slots(context.gc_context); Avm2::run_script_initializer(*self, context)?; diff --git a/core/src/avm2/vtable.rs b/core/src/avm2/vtable.rs index a316ff062..8e46a71fe 100644 --- a/core/src/avm2/vtable.rs +++ b/core/src/avm2/vtable.rs @@ -11,6 +11,7 @@ use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::QName; +use crate::context::UpdateContext; use crate::string::AvmString; use gc_arena::{Collect, GcCell, Mutation}; use std::cell::Ref; @@ -24,10 +25,6 @@ pub struct VTable<'gc>(GcCell<'gc, VTableData<'gc>>); #[derive(Collect, Clone)] #[collect(no_drop)] pub struct VTableData<'gc> { - /// should always be Some post-initialization - defining_class: Option>, - - /// should always be Some post-initialization scope: Option>, protected_namespace: Option>, @@ -55,8 +52,8 @@ pub struct VTableData<'gc> { #[derive(Collect, Clone)] #[collect(no_drop)] pub struct ClassBoundMethod<'gc> { - pub class: ClassObject<'gc>, - pub scope: ScopeChain<'gc>, + pub class: Option>, + pub scope: Option>, pub method: Method<'gc>, } @@ -65,7 +62,6 @@ impl<'gc> VTable<'gc> { VTable(GcCell::new( mc, VTableData { - defining_class: None, scope: None, protected_namespace: None, resolved_traits: PropertyMap::new(), @@ -87,7 +83,6 @@ impl<'gc> VTable<'gc> { let vt = VTable(GcCell::new( mc, VTableData { - defining_class: None, scope: None, protected_namespace: None, resolved_traits: rt, @@ -106,10 +101,6 @@ impl<'gc> VTable<'gc> { vt } - pub fn duplicate(self, mc: &Mutation<'gc>) -> Self { - VTable(GcCell::new(mc, self.0.read().clone())) - } - pub fn resolved_traits(&self) -> Ref<'_, PropertyMap<'gc, Property>> { Ref::map(self.0.read(), |v| &v.resolved_traits) } @@ -220,12 +211,13 @@ impl<'gc> VTable<'gc> { #[allow(clippy::if_same_then_else)] pub fn init_vtable( self, - defining_class: ClassObject<'gc>, + defining_class: Option>, + protected_namespace: Option>, traits: &[Trait<'gc>], - scope: ScopeChain<'gc>, + scope: Option>, superclass_vtable: Option, - activation: &mut Activation<'_, 'gc>, - ) -> Result<(), Error<'gc>> { + context: &mut UpdateContext<'_, 'gc>, + ) { // Let's talk about slot_ids and disp_ids. // Specification is one thing, but reality is another. @@ -277,15 +269,12 @@ impl<'gc> VTable<'gc> { // so long-term it's still something we should verify. // (and it's far from the only verification check we lack anyway) - let mut write = self.0.write(activation.context.gc_context); + let mut write = self.0.write(context.gc_context); let write = write.deref_mut(); - write.defining_class = Some(defining_class); - write.scope = Some(scope); + write.scope = scope; - write.protected_namespace = defining_class - .inner_class_definition() - .protected_namespace(); + write.protected_namespace = protected_namespace; if let Some(superclass_vtable) = superclass_vtable { write.resolved_traits = superclass_vtable.0.read().resolved_traits.clone(); @@ -434,11 +423,10 @@ impl<'gc> VTable<'gc> { } TraitKind::Slot { slot_id, .. } | TraitKind::Const { slot_id, .. } - | TraitKind::Function { slot_id, .. } | TraitKind::Class { slot_id, .. } => { let slot_id = *slot_id; - let value = trait_to_default_value(scope, trait_data, activation); + let value = trait_to_default_value(trait_data); let value = Some(value); let new_slot_id = if slot_id == 0 { @@ -467,36 +455,18 @@ impl<'gc> VTable<'gc> { type_name, unit, .. } => ( Property::new_slot(new_slot_id), - PropertyClass::name( - activation.context.gc_context, - type_name.clone(), - *unit, - ), - ), - TraitKind::Function { .. } => ( - Property::new_slot(new_slot_id), - PropertyClass::Class( - activation - .avm2() - .classes() - .function - .inner_class_definition(), - ), + PropertyClass::name(context.gc_context, type_name.clone(), *unit), ), TraitKind::Const { type_name, unit, .. } => ( Property::new_const_slot(new_slot_id), - PropertyClass::name( - activation.context.gc_context, - type_name.clone(), - *unit, - ), + PropertyClass::name(context.gc_context, type_name.clone(), *unit), ), TraitKind::Class { .. } => ( Property::new_const_slot(new_slot_id), PropertyClass::Class( - activation.avm2().classes().class.inner_class_definition(), + context.avm2.classes().class.inner_class_definition(), ), ), _ => unreachable!(), @@ -510,10 +480,9 @@ impl<'gc> VTable<'gc> { slot_classes[new_slot_id as usize] = new_class; } + TraitKind::Function { .. } => panic!("TraitKind::Function shouldn't appear"), } } - - Ok(()) } /// Retrieve a bound instance method suitable for use as a value. @@ -548,7 +517,13 @@ impl<'gc> VTable<'gc> { method, } = method; - FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class)) + FunctionObject::from_method( + activation, + method, + scope.expect("Scope should exist here"), + Some(receiver), + class, + ) } /// Install a const trait on the global object. @@ -606,20 +581,11 @@ impl<'gc> VTable<'gc> { } } -fn trait_to_default_value<'gc>( - scope: ScopeChain<'gc>, - trait_data: &Trait<'gc>, - activation: &mut Activation<'_, 'gc>, -) -> Value<'gc> { +fn trait_to_default_value<'gc>(trait_data: &Trait<'gc>) -> Value<'gc> { match trait_data.kind() { TraitKind::Slot { default_value, .. } => *default_value, TraitKind::Const { default_value, .. } => *default_value, - TraitKind::Function { function, .. } => { - FunctionObject::from_function(activation, *function, scope) - .unwrap() - .into() - } - TraitKind::Class { .. } => Value::Undefined, + TraitKind::Class { .. } => Value::Null, _ => unreachable!(), } }