avm2: Actually apply class refactor to ClassObject and describeType

This requires changing the AVM2 ClassObject symbol registry to use Classes
This commit is contained in:
Lord-McSweeney 2024-06-19 16:29:07 -07:00 committed by Lord-McSweeney
parent bb50a6f926
commit 9c872860e9
25 changed files with 153 additions and 174 deletions

View File

@ -564,7 +564,7 @@ impl<'gc> Class<'gc> {
param: None,
super_class: Some(activation.avm2().classes().class.inner_class_definition()),
attributes: ClassAttributes::FINAL,
protected_namespace: None,
protected_namespace,
direct_interfaces: Vec::new(),
all_interfaces: Vec::new(),
instance_allocator: Allocator(scriptobject_allocator),
@ -944,7 +944,7 @@ impl<'gc> Class<'gc> {
// Matching avmplus, this doesn't check whether the class is a
// c_class; it strips the suffix even for i_classes
if let Some(stripped) = local_name_wstr.strip_suffix('$' as u8) {
if let Some(stripped) = local_name_wstr.strip_suffix(b'$') {
let new_local_name = AvmString::new(mc, stripped);
QName::new(namespace, new_local_name)

View File

@ -519,15 +519,19 @@ pub fn load_player_globals<'gc>(
let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto));
domain.export_class(fn_classdef.name(), fn_classdef, mc);
let fn_c_class = fn_classdef
.c_class()
.expect("function::create_class returns an i_class");
// Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?;
object_class.link_type(mc, class_proto, class_class);
object_class.link_type(mc, class_proto, object_c_class);
fn_class.link_prototype(activation, fn_proto)?;
fn_class.link_type(mc, class_proto, class_class);
fn_class.link_type(mc, class_proto, fn_c_class);
class_class.link_prototype(activation, class_proto)?;
class_class.link_type(mc, class_proto, class_class);
class_class.link_type(mc, class_proto, class_c_class);
// At this point, we need at least a partial set of system classes in
// order to continue initializing the player. The rest of the classes
@ -561,11 +565,13 @@ pub fn load_player_globals<'gc>(
let global_class = ClassObject::from_class(activation, global_classdef, Some(object_class))?;
globals.set_proto(mc, global_class.prototype());
globals.set_instance_of(mc, global_class);
globals.set_instance_class(mc, global_classdef);
globals.set_vtable(mc, global_class.instance_vtable());
activation.context.avm2.toplevel_global_object = Some(globals);
script.set_global_class(mc, global_classdef);
script.set_global_class_obj(mc, global_class);
// From this point, `globals` is safe to be modified

View File

@ -1,12 +1,12 @@
use crate::avm2::class::Class;
pub use crate::avm2::globals::flash::utils::get_qualified_class_name;
use crate::avm2::metadata::Metadata;
use crate::avm2::method::Method;
use crate::avm2::object::{ArrayObject, TObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::property::Property;
use crate::avm2::{ClassObject, Namespace};
use crate::avm2::{Activation, Error, Object, Value};
use crate::avm2::{Activation, Error, Namespace, Object, Value};
use crate::avm2_stub_method;
// Implements `avmplus.describeTypeJSON`
@ -21,42 +21,44 @@ pub fn describe_type_json<'gc>(
}
let value = args[0].coerce_to_object(activation)?;
let class_obj = value.as_class_object().or_else(|| value.instance_of());
let class_def = value.instance_class();
let object = activation
.avm2()
.classes()
.object
.construct(activation, &[])?;
let Some(class_obj) = class_obj else {
let Some(class_def) = class_def else {
return Ok(Value::Null);
};
let is_static = value.as_class_object().is_some();
if !is_static && flags.contains(DescribeTypeFlags::USE_ITRAITS) {
return Ok(Value::Null);
let mut used_class_def = class_def;
if flags.contains(DescribeTypeFlags::USE_ITRAITS) {
if let Some(i_class) = used_class_def.i_class() {
used_class_def = i_class;
} else {
return Ok(Value::Null);
}
}
let class = class_obj.inner_class_definition();
let qualified_name = class
.name()
let qualified_name = used_class_def
.dollar_removed_name(activation.context.gc_context)
.to_qualified_name(activation.context.gc_context);
object.set_public_property("name", qualified_name.into(), activation)?;
object.set_public_property(
"isDynamic",
(is_static || !class.is_sealed()).into(),
(!used_class_def.is_sealed()).into(),
activation,
)?;
object.set_public_property("isFinal", used_class_def.is_final().into(), activation)?;
object.set_public_property(
"isFinal",
(is_static || class.is_final()).into(),
"isStatic",
value.as_class_object().is_some().into(),
activation,
)?;
object.set_public_property("isStatic", is_static.into(), activation)?;
let traits = describe_internal_body(activation, class_obj, is_static, flags)?;
let traits = describe_internal_body(activation, used_class_def, flags)?;
if flags.contains(DescribeTypeFlags::INCLUDE_TRAITS) {
object.set_public_property("traits", traits.into(), activation)?;
} else {
@ -181,14 +183,9 @@ fn describe_type_json_null<'gc>(
fn describe_internal_body<'gc>(
activation: &mut Activation<'_, 'gc>,
class_obj: ClassObject<'gc>,
is_static: bool,
class_def: Class<'gc>,
flags: DescribeTypeFlags,
) -> Result<Object<'gc>, Error<'gc>> {
// If we were passed a non-ClassObject, or the caller specifically requested it, then
// look at the instance "traits" (our implementation is different than avmplus)
let use_instance_traits = !is_static || flags.contains(DescribeTypeFlags::USE_ITRAITS);
let traits = activation
.avm2()
.classes()
@ -247,39 +244,24 @@ fn describe_internal_body<'gc>(
.as_array_storage_mut(activation.context.gc_context)
.unwrap();
let superclass = if use_instance_traits {
class_obj.superclass_object()
} else {
Some(activation.avm2().classes().class)
};
let superclass = class_def.super_class();
if flags.contains(DescribeTypeFlags::INCLUDE_BASES) {
let mut current_super_obj = superclass;
while let Some(super_obj) = current_super_obj {
let super_name = super_obj
.inner_class_definition()
let mut current_super_class = superclass;
while let Some(super_class) = current_super_class {
let super_name = super_class
.name()
.to_qualified_name(activation.context.gc_context);
bases_array.push(super_name.into());
current_super_obj = super_obj.superclass_object();
current_super_class = super_class.super_class();
}
}
// When we're describing a Class object, we use the class vtable (which hides instance properties)
let vtable = if use_instance_traits {
class_obj.instance_vtable()
} else {
class_obj.class_vtable()
};
let vtable = class_def.instance_vtable();
let super_vtable = class_def.super_class().map(|c| c.instance_vtable());
let super_vtable = if use_instance_traits {
class_obj.superclass_object().map(|c| c.instance_vtable())
} else {
class_obj.instance_class().map(|c| c.instance_vtable())
};
if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) && use_instance_traits {
for interface in &*class_obj.inner_class_definition().all_interfaces() {
if flags.contains(DescribeTypeFlags::INCLUDE_INTERFACES) {
for interface in &*class_def.all_interfaces() {
let interface_name = interface
.name()
.to_qualified_name(activation.context.gc_context);
@ -475,7 +457,7 @@ fn describe_internal_body<'gc>(
let accessor_type =
method_type.to_qualified_name_or_star(activation.context.gc_context);
let declared_by = defining_class
.name()
.dollar_removed_name(activation.context.gc_context)
.to_qualified_name(activation.context.gc_context);
let accessor_obj = activation
@ -524,11 +506,9 @@ fn describe_internal_body<'gc>(
}
}
let constructor = class_obj.inner_class_definition().instance_init();
let constructor = class_def.instance_init();
// Flash only shows a <constructor> element if it has at least one parameter
if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR)
&& use_instance_traits
&& !constructor.signature().is_empty()
if flags.contains(DescribeTypeFlags::INCLUDE_CONSTRUCTOR) && !constructor.signature().is_empty()
{
let params = write_params(&constructor, activation)?;
traits.set_public_property("constructor", params.into(), activation)?;

View File

@ -1,7 +1,7 @@
//! `Class` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::object::{Object, TObject};
use crate::avm2::value::Value;
@ -85,6 +85,7 @@ pub fn create_c_class<'gc>(
Method::from_builtin(class_init, "<Class class initializer>", gc_context),
gc_context,
);
class_c_class.set_attributes(gc_context, ClassAttributes::FINAL);
// 'length' is a weird undocumented constant in Class.
// We need to define it, since it shows up in 'describeType'

View File

@ -19,12 +19,12 @@ pub fn bitmap_allocator<'gc>(
class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let bitmap_cls = activation.avm2().classes().bitmap;
let bitmap_cls = activation.avm2().classes().bitmap.inner_class_definition();
let bitmapdata_cls = activation.context.avm2.classes().bitmapdata;
let mut class_object = Some(class);
let mut class_def = Some(class.inner_class_definition());
let orig_class = class;
while let Some(class) = class_object {
while let Some(class) = class_def {
if class == bitmap_cls {
let bitmap_data = BitmapDataWrapper::dummy(activation.context.gc_context);
let display_object = Bitmap::new_with_bitmap_data(
@ -77,7 +77,7 @@ pub fn bitmap_allocator<'gc>(
return Ok(obj);
}
}
class_object = class.superclass_object();
class_def = class.super_class();
}
unreachable!("A Bitmap subclass should have Bitmap in superclass chain");
}

View File

@ -96,7 +96,7 @@ pub fn init<'gc>(
// which makes custom classes see a disposed BitmapData before they call super()
let name = this.instance_class().map(|c| c.name());
let character = this
.instance_of()
.instance_class()
.and_then(|t| {
activation
.context

View File

@ -12,11 +12,11 @@ pub fn shape_allocator<'gc>(
class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let shape_cls = activation.avm2().classes().shape;
let shape_cls = activation.avm2().classes().shape.inner_class_definition();
let mut class_object = Some(class);
let mut class_def = Some(class.inner_class_definition());
let orig_class = class;
while let Some(class) = class_object {
while let Some(class) = class_def {
if class == shape_cls {
let display_object = Graphic::empty(&mut activation.context).into();
return initialize_for_allocator(activation, display_object, orig_class);
@ -36,7 +36,7 @@ pub fn shape_allocator<'gc>(
return initialize_for_allocator(activation, child, orig_class);
}
class_object = class.superclass_object();
class_def = class.super_class();
}
unreachable!("A Shape subclass should have Shape in superclass chain");
}

View File

@ -19,11 +19,15 @@ pub fn simple_button_allocator<'gc>(
) -> Result<Object<'gc>, Error<'gc>> {
use crate::vminterface::Instantiator;
let simplebutton_cls = activation.avm2().classes().simplebutton;
let simplebutton_cls = activation
.avm2()
.classes()
.simplebutton
.inner_class_definition();
let mut class_object = Some(class);
let mut class_def = Some(class.inner_class_definition());
let orig_class = class;
while let Some(class) = class_object {
while let Some(class) = class_def {
if class == simplebutton_cls {
let button = Avm2Button::empty_button(&mut activation.context);
// [NA] Buttons specifically need to PO'd
@ -48,7 +52,7 @@ pub fn simple_button_allocator<'gc>(
return initialize_for_allocator(activation, child, orig_class);
}
class_object = class.superclass_object();
class_def = class.super_class();
}
unreachable!("A SimpleButton subclass should have SimpleButton in superclass chain");
}

View File

@ -14,11 +14,11 @@ pub fn sprite_allocator<'gc>(
class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let sprite_cls = activation.avm2().classes().sprite;
let sprite_cls = activation.avm2().classes().sprite.inner_class_definition();
let mut class_object = Some(class);
let mut class_def = Some(class.inner_class_definition());
let orig_class = class;
while let Some(class) = class_object {
while let Some(class) = class_def {
if class == sprite_cls {
let movie = activation.caller_movie_or_root();
let display_object = MovieClip::new(movie, activation.context.gc_context).into();
@ -39,7 +39,7 @@ pub fn sprite_allocator<'gc>(
return initialize_for_allocator(activation, child, orig_class);
}
class_object = class.superclass_object();
class_def = class.super_class();
}
unreachable!("A Sprite subclass should have Sprite in superclass chain");
}

View File

@ -22,15 +22,15 @@ pub fn init<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(sound_object) = this.as_sound_object() {
let class_object = this
.instance_of()
let class_def = this
.instance_class()
.ok_or("Attempted to construct Sound on a bare object.")?;
if let Some((movie, symbol)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(class_object)
.class_symbol(class_def)
{
if let Some(Character::Sound(sound)) = activation
.context
@ -41,7 +41,7 @@ pub fn init<'gc>(
let sound = *sound;
sound_object.set_sound(&mut activation.context, sound)?;
} else {
tracing::warn!("Attempted to construct subclass of Sound, {}, which is associated with non-Sound character {}", class_object.inner_class_definition().name().local_name(), symbol);
tracing::warn!("Attempted to construct subclass of Sound, {}, which is associated with non-Sound character {}", class_def.name().local_name(), symbol);
}
}
}

View File

@ -7,9 +7,9 @@ pub fn video_allocator<'gc>(
class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let video_class = activation.avm2().classes().video;
let video_class = activation.avm2().classes().video.inner_class_definition();
let mut target_class = Some(class);
let mut target_class = Some(class.inner_class_definition());
while let Some(target) = target_class {
if target == video_class {
let movie = activation.caller_movie_or_root();
@ -32,7 +32,7 @@ pub fn video_allocator<'gc>(
return initialize_for_allocator(activation, child, class);
}
target_class = target.superclass_object();
target_class = target.super_class();
}
unreachable!("A Video subclass should have Video in superclass chain");

View File

@ -133,7 +133,7 @@ pub fn register_font<'gc>(
.context
.library
.avm2_class_registry()
.class_symbol(class)
.class_symbol(class.inner_class_definition())
{
if let Some(lib) = activation.context.library.library_for_movie(movie) {
if let Some(Character::Font(font)) = lib.character_by_id(id) {

View File

@ -17,11 +17,15 @@ pub fn text_field_allocator<'gc>(
class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let textfield_cls = activation.avm2().classes().textfield;
let textfield_cls = activation
.avm2()
.classes()
.textfield
.inner_class_definition();
let mut class_object = Some(class);
let mut class_def = Some(class.inner_class_definition());
let orig_class = class;
while let Some(class) = class_object {
while let Some(class) = class_def {
if class == textfield_cls {
let movie = activation.caller_movie_or_root();
let display_object =
@ -43,7 +47,7 @@ pub fn text_field_allocator<'gc>(
return initialize_for_allocator(activation, child, orig_class);
}
class_object = class.superclass_object();
class_def = class.super_class();
}
unreachable!("A TextField subclass should have TextField in superclass chain");
}

View File

@ -1,7 +1,7 @@
//! Function builtin and prototype
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::error::eval_error;
use crate::avm2::globals::array::resolve_array_hole;
use crate::avm2::method::{Method, NativeMethodImpl};
@ -239,6 +239,7 @@ pub fn create_class<'gc>(
Method::from_builtin(class_init, "<Function class initializer>", gc_context),
gc_context,
);
function_c_class.set_attributes(gc_context, ClassAttributes::FINAL);
function_i_class.set_c_class(gc_context, function_c_class);
function_c_class.set_i_class(gc_context, function_i_class);

View File

@ -1,7 +1,7 @@
//! Object builtin and prototype
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::{Method, NativeMethodImpl, ParamConfig};
use crate::avm2::object::{FunctionObject, Object, TObject};
use crate::avm2::traits::Trait;
@ -324,6 +324,7 @@ pub fn create_c_class<'gc>(
Method::from_builtin(class_init, "<Object class initializer>", gc_context),
gc_context,
);
object_c_class.set_attributes(gc_context, ClassAttributes::FINAL);
object_c_class.define_instance_trait(
gc_context,

View File

@ -1107,12 +1107,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get a raw pointer value for this object.
fn as_ptr(&self) -> *const ObjectPtr;
/// Get this object's class, if it has one.
fn instance_of(&self) -> Option<ClassObject<'gc>> {
let base = self.base();
base.instance_of()
}
/// Get this object's vtable, if it has one.
/// Every object with class should have a vtable
fn vtable(&self) -> Option<VTable<'gc>> {
@ -1127,7 +1121,8 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get this object's class's `Class`, if it has one.
fn instance_class(&self) -> Option<Class<'gc>> {
self.instance_of().map(|cls| cls.inner_class_definition())
let base = self.base();
base.instance_class()
}
/// Get this object's class's name, formatted for debug output.
@ -1137,11 +1132,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
.unwrap_or_else(|| "<Unknown type>".into())
}
fn set_instance_of(&self, mc: &Mutation<'gc>, instance_of: ClassObject<'gc>) {
let instance_vtable = instance_of.instance_vtable();
fn set_instance_class(&self, mc: &Mutation<'gc>, instance_class: Class<'gc>) {
let mut base = self.base_mut(mc);
base.set_instance_of(instance_of, instance_vtable);
base.set_instance_class(instance_class);
}
// Sets a different vtable for object, without changing instance_of.

View File

@ -19,7 +19,7 @@ pub fn byte_array_allocator<'gc>(
.context
.library
.avm2_class_registry()
.class_symbol(class)
.class_symbol(class.inner_class_definition())
{
if let Some(lib) = activation.context.library.library_for_movie(movie) {
if let Some(Character::BinaryData(binary_data)) = lib.character_by_id(id) {

View File

@ -112,19 +112,18 @@ impl<'gc> ClassObject<'gc> {
class: Class<'gc>,
superclass_object: Option<ClassObject<'gc>>,
) -> Result<Self, Error<'gc>> {
let c_class = class
.c_class()
.expect("Can only call ClassObject::from_class on i_classes");
let class_object = Self::from_class_partial(activation, class, superclass_object)?;
let class_proto = class_object.allocate_prototype(activation, superclass_object)?;
class_object.link_prototype(activation, class_proto)?;
let class_class = activation.avm2().classes().class;
let class_class_proto = class_class.prototype();
let class_class_proto = activation.avm2().classes().class.prototype();
class_object.link_type(
activation.context.gc_context,
class_class_proto,
class_class,
);
class_object.link_type(activation.context.gc_context, class_class_proto, c_class);
class_object.init_instance_vtable(activation)?;
class_object.into_finished_class(activation)
}
@ -146,10 +145,6 @@ impl<'gc> ClassObject<'gc> {
class: Class<'gc>,
superclass_object: Option<ClassObject<'gc>>,
) -> Result<Self, Error<'gc>> {
// We should only be able to construct `i_class` Classes
// (if i_class() is None it means that the Class is an i_class)
assert!(class.i_class().is_none());
let scope = activation.create_scopechain();
if let Some(base_class) = superclass_object.map(|b| b.inner_class_definition()) {
if base_class.is_final() {
@ -324,17 +319,10 @@ impl<'gc> ClassObject<'gc> {
/// This is intended to support initialization of early types such as
/// `Class` and `Object`. All other types should pull `Class`'s prototype
/// and type object from the `Avm2` instance.
pub fn link_type(
self,
gc_context: &Mutation<'gc>,
proto: Object<'gc>,
instance_of: ClassObject<'gc>,
) {
let instance_vtable = instance_of.instance_vtable();
pub fn link_type(self, gc_context: &Mutation<'gc>, proto: Object<'gc>, c_class: Class<'gc>) {
let mut write = self.0.write(gc_context);
write.base.set_instance_of(instance_of, instance_vtable);
write.base.set_instance_class(c_class);
write.base.set_proto(proto);
}

View File

@ -20,7 +20,7 @@ pub fn font_allocator<'gc>(
.context
.library
.avm2_class_registry()
.class_symbol(class)
.class_symbol(class.inner_class_definition())
{
if let Some(lib) = activation.context.library.library_for_movie(movie) {
if let Some(Character::Font(font)) = lib.character_by_id(id) {

View File

@ -53,9 +53,9 @@ pub struct ScriptObjectData<'gc> {
/// Implicit prototype of this script object.
proto: Option<Object<'gc>>,
/// The class object that this is an instance of.
/// The `Class` that this is an instance of.
/// If `none`, this is not an ES4 object at all.
instance_of: Option<ClassObject<'gc>>,
instance_class: Option<Class<'gc>>,
/// The table used for non-dynamic property lookups.
vtable: Option<VTable<'gc>>,
@ -143,7 +143,7 @@ impl<'gc> ScriptObjectData<'gc> {
slots: Vec::new(),
bound_methods: Vec::new(),
proto,
instance_of,
instance_class: instance_of.map(|cls| cls.inner_class_definition()),
vtable: instance_of.map(|cls| cls.instance_vtable()),
}
}
@ -390,13 +390,9 @@ impl<'gc> ScriptObjectData<'gc> {
*self.bound_methods.get_mut(disp_id as usize).unwrap() = Some(function);
}
/// Get the class object for this object, if it has one.
pub fn instance_of(&self) -> Option<ClassObject<'gc>> {
self.instance_of
}
/// Get the `Class` for this object, if it has one.
pub fn instance_class(&self) -> Option<Class<'gc>> {
self.instance_of.map(|cls| cls.inner_class_definition())
self.instance_class
}
/// Get the vtable for this object, if it has one.
@ -411,9 +407,8 @@ impl<'gc> ScriptObjectData<'gc> {
}
/// Set the class object for this object.
pub fn set_instance_of(&mut self, instance_of: ClassObject<'gc>, vtable: VTable<'gc>) {
self.instance_of = Some(instance_of);
self.vtable = Some(vtable);
pub fn set_instance_class(&mut self, instance_class: Class<'gc>) {
self.instance_class = Some(instance_class);
}
pub fn set_vtable(&mut self, vtable: VTable<'gc>) {

View File

@ -280,33 +280,30 @@ pub fn optimize<'gc>(
// but this works since it's guaranteed to be set in `Activation::from_method`.
let this_value = activation.local_register(0);
let (this_class, this_vtable) = if let Some(this_class) = activation.subclass_object() {
let this_class = if let Some(this_class) = activation.subclass_object() {
if this_value.is_of_type(activation, this_class.inner_class_definition()) {
(
Some(this_class),
Some(this_class.inner_class_definition().instance_vtable()),
)
} else if this_value
.as_object()
.and_then(|o| o.as_class_object())
.map(|c| c.inner_class_definition() == this_class.inner_class_definition())
.unwrap_or(false)
{
// Static method
(
Some(activation.avm2().classes().class),
Some(this_class.class_vtable()),
)
Some(this_class.inner_class_definition())
} else if let Some(this_object) = this_value.as_object() {
if this_object
.as_class_object()
.map(|c| c.inner_class_definition() == this_class.inner_class_definition())
.unwrap_or(false)
{
// Static method
this_object.instance_class()
} else {
None
}
} else {
(None, None)
None
}
} else {
(None, None)
None
};
let this_value = OptValue {
class: this_class.map(|c| c.inner_class_definition()),
vtable: this_vtable,
class: this_class,
vtable: this_class.map(|cls| cls.instance_vtable()),
contains_valid_integer: false,
contains_valid_unsigned: false,
null_state: NullState::NotNull,
@ -861,7 +858,7 @@ pub fn optimize<'gc>(
if !multiname.has_lazy_component() && has_simple_scoping {
let outer_scope = activation.outer();
if !outer_scope.is_empty() {
if let Some(this_vtable) = this_vtable {
if let Some(this_vtable) = this_class.map(|cls| cls.instance_vtable()) {
if this_vtable.has_trait(&multiname) {
*op = Op::GetScopeObject { index: 0 };

View File

@ -267,7 +267,7 @@ impl<'gc> TranslationUnit<'gc> {
self,
script_index,
global_obj,
global_classdef,
global_class,
domain,
activation,
)?;
@ -430,6 +430,9 @@ pub struct ScriptData<'gc> {
/// The class of this script's global object.
global_class: Option<Class<'gc>>,
/// The ClassObject of this script's global object.
global_class_obj: Option<ClassObject<'gc>>,
/// The domain associated with this script.
domain: Domain<'gc>,
@ -468,6 +471,7 @@ impl<'gc> Script<'gc> {
ScriptData {
globals,
global_class: None,
global_class_obj: None,
domain,
init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
@ -496,7 +500,7 @@ impl<'gc> Script<'gc> {
unit: TranslationUnit<'gc>,
script_index: u32,
globals: Object<'gc>,
global_class: Class<'gc>,
global_class_obj: ClassObject<'gc>,
domain: Domain<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Self, Error<'gc>> {
@ -513,7 +517,8 @@ impl<'gc> Script<'gc> {
activation.context.gc_context,
ScriptData {
globals,
global_class: Some(global_class),
global_class: Some(global_class_obj.inner_class_definition()),
global_class_obj: Some(global_class_obj),
domain,
init,
traits: Vec::new(),
@ -608,6 +613,10 @@ impl<'gc> Script<'gc> {
self.0.write(mc).global_class = Some(global_class);
}
pub fn set_global_class_obj(self, mc: &Mutation<'gc>, global_class_obj: ClassObject<'gc>) {
self.0.write(mc).global_class_obj = Some(global_class_obj);
}
/// Return the global scope for the script.
///
/// If the script has not yet been initialized, this will initialize it on
@ -628,7 +637,7 @@ impl<'gc> Script<'gc> {
globals.vtable().unwrap().init_vtable(
globals.instance_class().unwrap(),
globals.instance_of(),
self.0.read().global_class_obj,
&self.traits()?,
Some(scope),
None,

View File

@ -87,7 +87,7 @@ impl Avm2ObjectWindow {
.num_columns(2)
.striped(true)
.show(ui, |ui| {
if let Some(class) = object.instance_of() {
if let Some(class) = object.instance_class().and_then(|o| o.class_object()) {
ui.label("Instance Of");
show_avm2_value(ui, &mut activation.context, class.into(), messages);
ui.end_row();

View File

@ -4548,7 +4548,11 @@ impl<'gc, 'a> MovieClip<'gc> {
.context
.library
.avm2_class_registry_mut()
.set_class_symbol(class_object, movie.clone(), id);
.set_class_symbol(
class_object.inner_class_definition(),
movie.clone(),
id,
);
let library = activation
.context

View File

@ -1,5 +1,5 @@
use crate::avm1::{PropertyMap as Avm1PropertyMap, PropertyMap};
use crate::avm2::{ClassObject as Avm2ClassObject, Domain as Avm2Domain};
use crate::avm2::{Class as Avm2Class, Domain as Avm2Domain};
use crate::backend::audio::SoundHandle;
use crate::character::Character;
use std::borrow::Cow;
@ -48,7 +48,7 @@ impl WeakElement for WeakMovieSymbol {
pub struct Avm2ClassRegistry<'gc> {
/// A list of AVM2 class objects and the character IDs they are expected to
/// instantiate.
class_map: WeakValueHashMap<Avm2ClassObject<'gc>, WeakMovieSymbol>,
class_map: WeakValueHashMap<Avm2Class<'gc>, WeakMovieSymbol>,
}
unsafe impl Collect for Avm2ClassRegistry<'_> {
@ -76,24 +76,21 @@ impl<'gc> Avm2ClassRegistry<'gc> {
///
/// A value of `None` indicates that this AVM2 class is not associated with
/// a library symbol.
pub fn class_symbol(
&self,
class_object: Avm2ClassObject<'gc>,
) -> Option<(Arc<SwfMovie>, CharacterId)> {
match self.class_map.get(&class_object) {
pub fn class_symbol(&self, class_def: Avm2Class<'gc>) -> Option<(Arc<SwfMovie>, CharacterId)> {
match self.class_map.get(&class_def) {
Some(MovieSymbol(movie, symbol)) => Some((movie, symbol)),
None => None,
}
}
/// Associate an AVM2 class object with a given library symbol.
/// Associate an AVM2 class definition with a given library symbol.
pub fn set_class_symbol(
&mut self,
class_object: Avm2ClassObject<'gc>,
class_def: Avm2Class<'gc>,
movie: Arc<SwfMovie>,
symbol: CharacterId,
) {
if let Some(old) = self.class_map.get(&class_object) {
if let Some(old) = self.class_map.get(&class_def) {
if Arc::ptr_eq(&movie, &old.0) && symbol != old.1 {
// Flash player actually allows using the same class in multiple SymbolClass
// entries in the same swf, with *different* symbol ids. Whichever one
@ -102,7 +99,7 @@ impl<'gc> Avm2ClassRegistry<'gc> {
// of deliberately crafted SWFs.
tracing::warn!(
"Tried to overwrite class {:?} id={:?} with symbol id={:?} from same movie",
class_object,
class_def,
old.1,
symbol,
);
@ -114,8 +111,7 @@ impl<'gc> Avm2ClassRegistry<'gc> {
// instantiates the clip on the timeline.
return;
}
self.class_map
.insert(class_object, MovieSymbol(movie, symbol));
self.class_map.insert(class_def, MovieSymbol(movie, symbol));
}
}