avm2: Refactor Vector.<T> class creation

This commit is contained in:
Adrian Wielgosik 2023-07-17 22:42:34 +02:00 committed by Adrian Wielgosik
parent 8be0a36b1a
commit f504e9d0b4
7 changed files with 216 additions and 254 deletions

View File

@ -78,7 +78,7 @@ pub struct Class<'gc> {
name: QName<'gc>,
/// The type parameter for this class (only supported for Vector)
param: Option<GcCell<'gc, Class<'gc>>>,
param: Option<Option<GcCell<'gc, Class<'gc>>>>,
/// The name of this class's superclass.
super_class: Option<Multiname<'gc>>,
@ -139,12 +139,6 @@ pub struct Class<'gc> {
/// If None, a simple coercion is done.
call_handler: Option<Method<'gc>>,
/// The class initializer for specializations of this class.
///
/// Only applies for generic classes. Must be called once and only once
/// per specialization, prior to any use of the specialized class.
specialized_class_init: Method<'gc>,
/// Static traits for a given class.
///
/// These are accessed as class object properties.
@ -156,7 +150,7 @@ pub struct Class<'gc> {
/// Maps a type parameter to the application of this class with that parameter.
///
/// Only applicable if this class is generic.
applications: FnvHashMap<ClassKey<'gc>, GcCell<'gc, Class<'gc>>>,
applications: FnvHashMap<Option<ClassKey<'gc>>, GcCell<'gc, Class<'gc>>>,
/// Whether or not this is a system-defined class.
///
@ -220,11 +214,6 @@ impl<'gc> Class<'gc> {
class_initializer_called: false,
call_handler: None,
class_traits: Vec::new(),
specialized_class_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Null specialization constructor>",
mc,
),
traits_loaded: true,
is_system: true,
applications: FnvHashMap::default(),
@ -232,45 +221,55 @@ impl<'gc> Class<'gc> {
)
}
pub fn add_application(
&mut self,
param: Option<GcCell<'gc, Class<'gc>>>,
cls: GcCell<'gc, Class<'gc>>,
) {
let key = param.map(ClassKey);
self.applications.insert(key, cls);
}
/// Apply type parameters to an existing class.
///
/// This is used to parameterize a generic type. The returned class will no
/// longer be generic.
pub fn with_type_param(
this: GcCell<'gc, Class<'gc>>,
param: GcCell<'gc, Class<'gc>>,
param: Option<GcCell<'gc, Class<'gc>>>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Class<'gc>> {
let read = this.read();
let key = ClassKey(param);
let key = param.map(ClassKey);
if let Some(application) = read.applications.get(&key) {
return *application;
}
let mut new_class = (*read).clone();
// This can only happen for non-builtin Vector types,
// so let's create one here directly.
new_class.param = Some(param);
new_class.attributes.remove(ClassAttributes::GENERIC);
new_class.class_init = new_class.specialized_class_init.clone();
new_class.class_initializer_called = false;
let object_vector_cls = read
.applications
.get(&None)
.expect("Vector.<*> not initialized?");
let param = param.expect("Trying to create Vector<*>, which shouldn't happen here");
let name = format!("Vector.<{}>", param.read().name().to_qualified_name(mc));
let new_class = Self::new(
// FIXME - we should store a `Multiname` instead of a `QName`, and use the
// `params` field. For now, this is good enough to get tests passing
let name_with_params = format!(
"{}.<{}>",
new_class.name.local_name(),
param.read().name().to_qualified_name(mc)
QName::new(read.name.namespace(), AvmString::new_utf8(mc, name)),
Some(Multiname::new(read.name.namespace(), "Vector.<*>")),
object_vector_cls.read().instance_init(),
object_vector_cls.read().class_init(),
mc,
);
new_class.write(mc).param = Some(Some(param));
new_class.write(mc).call_handler = object_vector_cls.read().call_handler();
new_class.name = QName::new(
new_class.name.namespace(),
AvmString::new_utf8(mc, name_with_params),
);
let new_class = GcCell::new(mc, new_class);
drop(read);
this.write(mc).applications.insert(key, new_class);
new_class
}
@ -395,11 +394,6 @@ impl<'gc> Class<'gc> {
class_initializer_called: false,
call_handler: native_call_handler,
class_traits: Vec::new(),
specialized_class_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Null specialization constructor>",
activation.context.gc_context,
),
traits_loaded: false,
is_system: false,
applications: Default::default(),
@ -564,11 +558,6 @@ impl<'gc> Class<'gc> {
"<Activation object class constructor>",
activation.context.gc_context,
),
specialized_class_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Activation object specialization constructor>",
activation.context.gc_context,
),
class_initializer_called: false,
call_handler: None,
class_traits: Vec::new(),
@ -587,6 +576,10 @@ impl<'gc> Class<'gc> {
self.name = name;
}
pub fn set_param(&mut self, param: Option<Option<GcCell<'gc, Class<'gc>>>>) {
self.param = param;
}
pub fn super_class_name(&self) -> &Option<Multiname<'gc>> {
&self.super_class
}
@ -815,11 +808,6 @@ impl<'gc> Class<'gc> {
self.class_initializer_called = true;
}
/// Set the class initializer for specializations of this class.
pub fn set_specialized_init(&mut self, specialized_init: Method<'gc>) {
self.specialized_class_init = specialized_init;
}
pub fn direct_interfaces(&self) -> &[Multiname<'gc>] {
&self.direct_interfaces
}
@ -847,10 +835,6 @@ impl<'gc> Class<'gc> {
pub fn is_generic(&self) -> bool {
self.attributes.contains(ClassAttributes::GENERIC)
}
pub fn param(&self) -> &Option<GcCell<'gc, Class<'gc>>> {
&self.param
}
}
pub struct ClassHashWrapper<'gc>(pub GcCell<'gc, Class<'gc>>);

View File

@ -183,9 +183,15 @@ impl<'gc> Domain<'gc> {
if let Some(param) = multiname.param() {
if !param.is_any_name() {
if let Some(resolved_param) = self.get_class(&param, mc)? {
return Ok(Some(Class::with_type_param(class, resolved_param, mc)));
return Ok(Some(Class::with_type_param(
class,
Some(resolved_param),
mc,
)));
}
return Ok(None);
} else {
return Ok(Some(Class::with_type_param(class, None, mc)));
}
}
}

View File

@ -83,7 +83,8 @@ pub struct SystemClasses<'gc> {
pub sprite: ClassObject<'gc>,
pub simplebutton: ClassObject<'gc>,
pub regexp: ClassObject<'gc>,
pub vector: ClassObject<'gc>,
pub generic_vector: ClassObject<'gc>,
pub object_vector: ClassObject<'gc>, // Vector.<*>
pub soundtransform: ClassObject<'gc>,
pub soundchannel: ClassObject<'gc>,
pub bitmap: ClassObject<'gc>,
@ -203,7 +204,8 @@ impl<'gc> SystemClasses<'gc> {
sprite: object,
simplebutton: object,
regexp: object,
vector: object,
generic_vector: object,
object_vector: object,
soundtransform: object,
soundchannel: object,
bitmap: object,
@ -364,6 +366,41 @@ fn class<'gc>(
Ok(class_object)
}
fn vector_class<'gc>(
param_class: Option<ClassObject<'gc>>,
legacy_name: &'static str,
script: Script<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<ClassObject<'gc>, Error<'gc>> {
let mc = activation.context.gc_context;
let (_, mut global, mut domain) = script.init();
let cls = param_class.map(|c| c.inner_class_definition());
let vector_cls = class(
vector::create_builtin_class(activation, cls),
script,
activation,
)?;
vector_cls.set_param(activation, Some(param_class));
let generic_vector = activation.avm2().classes().generic_vector;
generic_vector.add_application(activation, param_class, vector_cls);
let generic_cls = generic_vector.inner_class_definition();
generic_cls
.write(mc)
.add_application(cls, 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(),
activation.avm2().classes().class,
);
domain.export_definition(legacy_name, script, mc);
Ok(vector_cls)
}
macro_rules! avm2_system_class {
($field:ident, $activation:ident, $class:expr, $script:expr) => {
let class_object = class($class, $script, $activation)?;
@ -569,7 +606,38 @@ pub fn load_player_globals<'gc>(
)?;
function(activation, "", "unescape", toplevel::unescape, script)?;
avm2_system_class!(vector, activation, vector::create_class(activation), script);
avm2_system_class!(
generic_vector,
activation,
vector::create_generic_class(activation),
script
);
vector_class(
Some(activation.avm2().classes().int),
"Vector$int",
script,
activation,
)?;
vector_class(
Some(activation.avm2().classes().uint),
"Vector$uint",
script,
activation,
)?;
vector_class(
Some(activation.avm2().classes().number),
"Vector$double",
script,
activation,
)?;
let object_vector = vector_class(None, "Vector$object", script, activation)?;
activation
.avm2()
.system_classes
.as_mut()
.unwrap()
.object_vector = object_vector;
avm2_system_class!(date, activation, date::create_class(activation), script);

View File

@ -2,12 +2,15 @@
use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::error::type_error;
use crate::avm2::globals::array::{
compare_numeric, compare_string_case_insensitive, compare_string_case_sensitive, ArrayIter,
SortOptions,
};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::object::{vector_allocator, FunctionObject, Object, TObject, VectorObject};
use crate::avm2::object::{
vector_allocator, ClassObject, FunctionObject, Object, TObject, VectorObject,
};
use crate::avm2::value::Value;
use crate::avm2::vector::VectorStorage;
use crate::avm2::Error;
@ -17,6 +20,17 @@ use crate::string::AvmString;
use gc_arena::GcCell;
use std::cmp::{max, min, Ordering};
pub fn generic_vector_allocator<'gc>(
_class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
return Err(Error::AvmError(type_error(
activation,
"Error #1007: Instantiation attempted on a non-constructor.",
1007,
)?));
}
/// Implements `Vector`'s instance constructor.
pub fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc>,
@ -56,7 +70,7 @@ fn class_call<'gc>(
let this_class = activation.subclass_object().unwrap();
let value_type = this_class
.as_class_params()
.ok_or("Cannot convert to Vector")? // note: ideally, an untyped Vector shouldn't have a call handler at all
.ok_or("Cannot convert to Vector")? // technically unreachable
.unwrap_or(activation.avm2().classes().object);
let arg = args.get(0).cloned().unwrap();
@ -84,96 +98,15 @@ fn class_call<'gc>(
Ok(VectorObject::from_vector(new_storage, activation)?.into())
}
/// Implements `Vector`'s class constructor.
pub fn class_init<'gc>(
pub fn generic_init<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let mut globals = activation.global_scope().unwrap();
let mut domain = activation.domain();
let vector_internal_namespace = activation.avm2().vector_internal_namespace;
//We have to grab Object's defining script instead of our own, because
//at this point Vector hasn't actually been defined yet. It doesn't
//matter because we only have one script for our globals.
let (_, script) = domain
.get_defining_script(&Multiname::new(
activation.avm2().public_namespace,
"Object",
))?
.unwrap();
let class_class = activation.avm2().classes().class;
let int_class = activation.avm2().classes().int;
let int_vector_class = this.apply(activation, int_class.into())?;
let int_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$int");
globals.install_const_late(
activation.context.gc_context,
int_vector_name_legacy,
int_vector_class.into(),
class_class,
);
domain.export_definition(
int_vector_name_legacy,
script,
activation.context.gc_context,
);
let uint_class = activation.avm2().classes().uint;
let uint_vector_class = this.apply(activation, uint_class.into())?;
let uint_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$uint");
globals.install_const_late(
activation.context.gc_context,
uint_vector_name_legacy,
uint_vector_class.into(),
class_class,
);
domain.export_definition(
uint_vector_name_legacy,
script,
activation.context.gc_context,
);
let number_class = activation.avm2().classes().number;
let number_vector_class = this.apply(activation, number_class.into())?;
let number_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$double");
globals.install_const_late(
activation.context.gc_context,
number_vector_name_legacy,
number_vector_class.into(),
class_class,
);
domain.export_definition(
number_vector_name_legacy,
script,
activation.context.gc_context,
);
let plain_vector_class = this.apply(activation, Value::Null)?;
let object_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$object");
globals.install_const_late(
activation.context.gc_context,
object_vector_name_legacy,
plain_vector_class.into(),
class_class,
);
domain.export_definition(
object_vector_name_legacy,
script,
activation.context.gc_context,
);
Ok(Value::Undefined)
activation.super_init(this, args)
}
/// Implements `Vector`'s specialized-class constructor.
pub fn specialized_class_init<'gc>(
fn class_init<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
@ -307,9 +240,6 @@ pub fn concat<'gc>(
return Err("Not a vector-structured object".into());
};
let my_class = this
.instance_of()
.ok_or("TypeError: Tried to concat into a bare object")?;
let val_class = new_vector_storage.value_type().inner_class_definition();
for arg in args {
@ -319,11 +249,16 @@ pub fn concat<'gc>(
let arg_class = arg_obj
.instance_of_class_definition()
.ok_or("TypeError: Tried to concat from a bare object")?;
if !arg.is_of_type(activation, my_class.inner_class_definition()) {
// this is Vector.<int/uint/Number/*>
let my_base_vector_class = activation
.subclass_object()
.expect("Method call without bound class?");
if !arg.is_of_type(activation, my_base_vector_class.inner_class_definition()) {
return Err(format!(
"TypeError: Cannot coerce argument of type {:?} to argument of type {:?}",
arg_class.read().name(),
my_class.inner_class_definition().read().name()
my_base_vector_class.inner_class_definition().read().name()
)
.into());
}
@ -945,28 +880,61 @@ pub fn splice<'gc>(
}
/// Construct `Vector`'s class.
pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context;
let class = Class::new(
QName::new(activation.avm2().vector_public_namespace, "Vector"),
Some(Multiname::new(activation.avm2().public_namespace, "Object")),
Method::from_builtin(instance_init, "<Vector instance initializer>", mc),
Method::from_builtin(class_init, "<Vector class initializer>", mc),
Method::from_builtin(generic_init, "<Vector instance initializer>", mc),
Method::from_builtin(generic_init, "<Vector class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::GENERIC | ClassAttributes::FINAL);
write.set_instance_allocator(generic_vector_allocator);
class
}
/// Construct `Vector.<int/uint/Number/*>`'s class.
pub fn create_builtin_class<'gc>(
activation: &mut Activation<'_, 'gc>,
param: Option<GcCell<'gc, Class<'gc>>>,
) -> GcCell<'gc, Class<'gc>> {
let mc = activation.context.gc_context;
// FIXME - we should store a `Multiname` instead of a `QName`, and use the
// `params` field. For now, this is good enough to get tests passing
let name = if let Some(param) = param {
let name = format!("Vector.<{}>", param.read().name().to_qualified_name(mc));
QName::new(
activation.avm2().vector_public_namespace,
AvmString::new_utf8(mc, name),
)
} else {
QName::new(activation.avm2().vector_public_namespace, "Vector.<*>")
};
let class = Class::new(
name,
Some(Multiname::new(activation.avm2().public_namespace, "Object")),
Method::from_builtin(instance_init, "<Vector.<T> instance initializer>", mc),
Method::from_builtin(class_init, "<Vector.<T> class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::GENERIC | ClassAttributes::FINAL);
// TODO: Vector.<*> is also supposed to be final, but currently
// that'd make it impossible for us to create derived Vector.<MyType>.
if param.is_some() {
write.set_attributes(ClassAttributes::FINAL);
}
write.set_param(Some(param));
write.set_instance_allocator(vector_allocator);
write.set_specialized_init(Method::from_builtin(
specialized_class_init,
"<Vector specialized class initializer>",
mc,
));
write.set_call_handler(Method::from_builtin(
class_call,
"<Vector call handler>",
"<Vector.<T> call handler>",
mc,
));

View File

@ -465,15 +465,6 @@ impl<'gc> ClassObject<'gc> {
return true;
}
let test_class_read = test_class.read();
if let (Some(Some(my_param)), Some(other_single_param)) =
(class.as_class_params(), test_class_read.param())
{
if my_param.has_class_in_chain(*other_single_param) {
return true;
}
}
my_class = class.superclass_object()
}
@ -727,6 +718,18 @@ impl<'gc> ClassObject<'gc> {
}
}
pub fn add_application(
&self,
activation: &mut Activation<'_, 'gc>,
param: Option<ClassObject<'gc>>,
cls: ClassObject<'gc>,
) {
self.0
.write(activation.context.gc_context)
.applications
.insert(param, cls);
}
pub fn translation_unit(self) -> Option<TranslationUnit<'gc>> {
if let Method::Bytecode(bc) = self.0.read().constructor {
Some(bc.txunit)
@ -780,6 +783,14 @@ impl<'gc> ClassObject<'gc> {
self.0.read().superclass_object
}
pub fn set_param(
self,
activation: &mut Activation<'_, 'gc>,
param: Option<Option<ClassObject<'gc>>>,
) {
self.0.write(activation.context.gc_context).params = param;
}
pub fn as_class_params(self) -> Option<Option<ClassObject<'gc>>> {
self.0.read().params
}
@ -908,10 +919,6 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
return Err(format!("Class {:?} is not generic", self_class.read().name()).into());
}
if !self_class.read().param().is_none() {
return Err(format!("Class {:?} was already applied", self_class.read().name()).into());
}
//Because `null` is a valid parameter, we have to accept values as
//parameters instead of objects. We coerce them to objects now.
let object_param = match nullable_param {
@ -933,75 +940,23 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
return Ok(*application);
}
let class_param = object_param
.unwrap_or(activation.avm2().classes().object)
.inner_class_definition();
// if it's not a known application, then it's not int/uint/Number/*,
// so it must be a simple Vector.<*>-derived class.
let class_param = object_param.map(|c| c.inner_class_definition());
let parameterized_class: GcCell<'_, Class<'_>> =
Class::with_type_param(self_class, class_param, activation.context.gc_context);
let class_scope = self.0.read().class_scope;
let instance_scope = self.0.read().instance_scope;
let instance_allocator = self.0.read().instance_allocator.clone();
let superclass_object = self.0.read().superclass_object;
// NOTE: this isn't fully accurate, but much simpler.
// FP's Vector is more of special case that literally copies some parent class's properties
// main example: Vector.<Object>.prototype === Vector.<*>.prototype
let class_proto = self.allocate_prototype(activation, superclass_object)?;
let vector_star_cls = activation.avm2().classes().object_vector;
let class_object =
Self::from_class(activation, parameterized_class, Some(vector_star_cls))?;
let class_class = activation.avm2().classes().class;
let constructor = self.0.read().constructor.clone();
let native_constructor = self.0.read().native_constructor.clone();
let call_handler = self.0.read().call_handler.clone();
let mut class_object = ClassObject(GcCell::new(
activation.context.gc_context,
ClassObjectData {
base: ScriptObjectData::new(class_class),
class: parameterized_class,
prototype: None,
class_scope,
instance_scope,
superclass_object,
instance_allocator,
constructor,
native_constructor,
call_handler,
params: Some(object_param),
applications: Default::default(),
interfaces: Vec::new(),
instance_vtable: VTable::empty(activation.context.gc_context),
class_vtable: VTable::empty(activation.context.gc_context),
},
));
class_object
.inner_class_definition()
.read()
.validate_class(class_object.superclass_object())?;
class_object.instance_vtable().init_vtable(
class_object,
parameterized_class.read().instance_traits(),
class_object.instance_scope(),
class_object
.superclass_object()
.map(|cls| cls.instance_vtable()),
activation,
)?;
// class vtable == class traits + Class instance traits
class_object.class_vtable().init_vtable(
class_object,
parameterized_class.read().class_traits(),
class_object.class_scope(),
Some(class_object.instance_of().unwrap().instance_vtable()),
activation,
)?;
class_object.link_prototype(activation, class_proto)?;
class_object.link_interfaces(activation)?;
class_object.install_class_vtable_and_slots(activation.context.gc_context);
class_object.run_class_initializer(activation)?;
class_object.0.write(activation.context.gc_context).params = Some(object_param);
self.0
.write(activation.context.gc_context)

View File

@ -71,7 +71,7 @@ impl<'gc> VectorObject<'gc> {
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let value_type = vector.value_type();
let vector_class = activation.avm2().classes().vector;
let vector_class = activation.avm2().classes().generic_vector;
let applied_class = vector_class.apply(activation, value_type.into())?;

View File

@ -8,7 +8,6 @@ use crate::avm2::script::TranslationUnit;
use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::Namespace;
use crate::avm2::QName;
use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32};
use crate::string::{AvmAtom, AvmString, WStr};
use gc_arena::{Collect, GcCell, MutationContext};
@ -1029,24 +1028,6 @@ impl<'gc> Value<'gc> {
if object.is_of_type(class, &mut activation.context) {
return Ok(*self);
}
if let Some(vector) = object.as_vector_storage() {
let name = class.read().name();
let vector_public_namespace = activation.avm2().vector_public_namespace;
let vector_internal_namespace = activation.avm2().vector_internal_namespace;
if name == QName::new(vector_public_namespace, "Vector")
|| (name == QName::new(vector_internal_namespace, "Vector$int")
&& vector.value_type() == activation.avm2().classes().int)
|| (name == QName::new(vector_internal_namespace, "Vector$uint")
&& vector.value_type() == activation.avm2().classes().uint)
|| (name == QName::new(vector_internal_namespace, "Vector$number")
&& vector.value_type() == activation.avm2().classes().number)
|| (name == QName::new(vector_internal_namespace, "Vector$object")
&& vector.value_type() == activation.avm2().classes().object)
{
return Ok(*self);
}
}
}
let name = class