avm2: Convert `has_class_in_chain` to use a `GcCell<Class>` (#11494)

This commit is contained in:
Aaron Hill 2023-07-07 16:22:38 -04:00 committed by GitHub
parent b68008426a
commit 61c50e636a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 300 additions and 224 deletions

View File

@ -433,7 +433,7 @@ impl<'gc> Avm2<'gc> {
if let Some(object) = object.and_then(|obj| obj.upgrade(context.gc_context)) {
let mut activation = Activation::from_nothing(context.reborrow());
if object.is_of_type(on_type, &mut activation.context) {
if object.is_of_type(on_type.inner_class_definition(), &mut activation.context) {
if let Err(err) = events::dispatch_event(&mut activation, object, event) {
tracing::error!(
"Encountered AVM2 error when broadcasting `{}` event: {:?}",

View File

@ -909,7 +909,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
matches = true;
} else if let Ok(err_object) = err_object {
let type_name = self.pool_multiname_static(method, e.type_name)?;
let ty_class = self.resolve_class(&type_name)?;
let ty_class = self.resolve_class(&type_name)?.inner_class_definition();
matches = err_object.is_of_type(ty_class, &mut self.context);
}
@ -2105,8 +2105,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
}
fn op_check_filter(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let xml = self.avm2().classes().xml;
let xml_list = self.avm2().classes().xml_list;
let xml = self.avm2().classes().xml.inner_class_definition();
let xml_list = self.avm2().classes().xml_list.inner_class_definition();
let value = self.pop_stack().coerce_to_object_or_typeerror(self, None)?;
if value.is_of_type(xml, &mut self.context) || value.is_of_type(xml_list, &mut self.context)
@ -2791,7 +2791,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let value = self.pop_stack();
let multiname = self.pool_multiname_static(method, type_name_index)?;
let type_object = self.resolve_class(&multiname)?;
let type_object = self.resolve_class(&multiname)?.inner_class_definition();
let is_instance_of = value.is_of_type(self, type_object);
self.push_stack(is_instance_of);
@ -2804,7 +2804,8 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.pop_stack()
.as_object()
.and_then(|o| o.as_class_object())
.ok_or("Cannot check if value is of a type that is null, undefined, or not a class")?;
.ok_or("Cannot check if value is of a type that is null, undefined, or not a class")?
.inner_class_definition();
let value = self.pop_stack();
let is_instance_of = value.is_of_type(self, type_object);
@ -2821,7 +2822,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let value = self.pop_stack();
let multiname = self.pool_multiname_static(method, type_name_index)?;
let class = self.resolve_class(&multiname)?;
let class = self.resolve_class(&multiname)?.inner_class_definition();
if value.is_of_type(self, class) {
self.push_stack(value);
@ -2847,7 +2848,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
)?))?;
let value = self.pop_stack();
if value.is_of_type(self, class) {
if value.is_of_type(self, class.inner_class_definition()) {
self.push_stack(value);
} else {
self.push_stack(Value::Null);

View File

@ -11,6 +11,7 @@ use crate::avm2::Multiname;
use crate::avm2::Namespace;
use crate::avm2::QName;
use bitflags::bitflags;
use fnv::FnvHashMap;
use gc_arena::{Collect, GcCell, MutationContext};
use std::fmt;
use std::hash::{Hash, Hasher};
@ -152,6 +153,11 @@ pub struct Class<'gc> {
/// Whether or not this `Class` has loaded its traits or not.
traits_loaded: bool,
/// 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>>>,
/// Whether or not this is a system-defined class.
///
/// System defined classes are allowed to have illegal trait configurations
@ -159,6 +165,26 @@ pub struct Class<'gc> {
is_system: bool,
}
/// Allows using a `GcCell<'gc, Class<'gc>>` as a HashMap key,
/// using the pointer address for hashing/equality.
#[derive(Collect, Copy, Clone)]
#[collect(no_drop)]
struct ClassKey<'gc>(GcCell<'gc, Class<'gc>>);
impl PartialEq for ClassKey<'_> {
fn eq(&self, other: &Self) -> bool {
GcCell::ptr_eq(self.0, other.0)
}
}
impl Eq for ClassKey<'_> {}
impl Hash for ClassKey<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_ptr().hash(state);
}
}
impl<'gc> Class<'gc> {
/// Create a new class.
///
@ -201,6 +227,7 @@ impl<'gc> Class<'gc> {
),
traits_loaded: true,
is_system: true,
applications: FnvHashMap::default(),
},
)
}
@ -210,11 +237,18 @@ impl<'gc> Class<'gc> {
/// This is used to parameterize a generic type. The returned class will no
/// longer be generic.
pub fn with_type_param(
&self,
this: GcCell<'gc, Class<'gc>>,
param: GcCell<'gc, Class<'gc>>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Class<'gc>> {
let mut new_class = self.clone();
let read = this.read();
let key = ClassKey(param);
if let Some(application) = read.applications.get(&key) {
return *application;
}
let mut new_class = (*read).clone();
new_class.param = Some(param);
new_class.attributes.remove(ClassAttributes::GENERIC);
@ -234,7 +268,11 @@ impl<'gc> Class<'gc> {
AvmString::new_utf8(mc, name_with_params),
);
GcCell::allocate(mc, new_class)
let new_class = GcCell::allocate(mc, new_class);
drop(read);
this.write(mc).applications.insert(key, new_class);
new_class
}
/// Set the attributes of the class (sealed/final/interface status).
@ -364,6 +402,7 @@ impl<'gc> Class<'gc> {
),
traits_loaded: false,
is_system: false,
applications: Default::default(),
},
))
}
@ -473,7 +512,7 @@ impl<'gc> Class<'gc> {
}
if instance_trait.is_override() && !did_override {
return Err(format!("VerifyError: Trait {} in class {} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name().local_name()).into());
return Err(format!("VerifyError: Trait {} in class {:?} marked as override, does not override any other trait", instance_trait.name().local_name(), self.name()).into());
}
}
}
@ -535,6 +574,7 @@ impl<'gc> Class<'gc> {
class_traits: Vec::new(),
traits_loaded: true,
is_system: false,
applications: Default::default(),
},
))
}

View File

@ -149,7 +149,7 @@ impl<'gc> Domain<'gc> {
Ok(None)
}
pub fn get_class(
fn get_class_inner(
self,
multiname: &Multiname<'gc>,
) -> Result<Option<GcCell<'gc, Class<'gc>>>, Error<'gc>> {
@ -159,12 +159,32 @@ impl<'gc> Domain<'gc> {
}
if let Some(parent) = read.parent {
return parent.get_class(multiname);
return parent.get_class_inner(multiname);
}
Ok(None)
}
pub fn get_class(
self,
multiname: &Multiname<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<Option<GcCell<'gc, Class<'gc>>>, Error<'gc>> {
let class = self.get_class_inner(multiname)?;
if let Some(class) = class {
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(None);
}
}
}
Ok(class)
}
/// Resolve a Multiname and return the script that provided it.
///
/// If a name does not exist or cannot be resolved, an error will be thrown.

View File

@ -51,56 +51,96 @@ impl FilterAvm2Ext for Filter {
activation: &mut Activation<'_, 'gc>,
object: Object<'gc>,
) -> Result<Filter, Error<'gc>> {
let bevel_filter = activation.avm2().classes().bevelfilter;
let bevel_filter = activation
.avm2()
.classes()
.bevelfilter
.inner_class_definition();
if object.is_of_type(bevel_filter, &mut activation.context) {
return avm2_to_bevel_filter(activation, object);
}
let blur_filter = activation.avm2().classes().blurfilter;
let blur_filter = activation
.avm2()
.classes()
.blurfilter
.inner_class_definition();
if object.is_of_type(blur_filter, &mut activation.context) {
return avm2_to_blur_filter(activation, object);
}
let color_matrix_filter = activation.avm2().classes().colormatrixfilter;
let color_matrix_filter = activation
.avm2()
.classes()
.colormatrixfilter
.inner_class_definition();
if object.is_of_type(color_matrix_filter, &mut activation.context) {
return avm2_to_color_matrix_filter(activation, object);
}
let convolution_filter = activation.avm2().classes().convolutionfilter;
let convolution_filter = activation
.avm2()
.classes()
.convolutionfilter
.inner_class_definition();
if object.is_of_type(convolution_filter, &mut activation.context) {
return avm2_to_convolution_filter(activation, object);
}
let displacement_map_filter = activation.avm2().classes().displacementmapfilter;
let displacement_map_filter = activation
.avm2()
.classes()
.displacementmapfilter
.inner_class_definition();
if object.is_of_type(displacement_map_filter, &mut activation.context) {
return avm2_to_displacement_map_filter(activation, object);
}
let drop_shadow_filter = activation.avm2().classes().dropshadowfilter;
let drop_shadow_filter = activation
.avm2()
.classes()
.dropshadowfilter
.inner_class_definition();
if object.is_of_type(drop_shadow_filter, &mut activation.context) {
return avm2_to_drop_shadow_filter(activation, object);
}
let glow_filter = activation.avm2().classes().glowfilter;
let glow_filter = activation
.avm2()
.classes()
.glowfilter
.inner_class_definition();
if object.is_of_type(glow_filter, &mut activation.context) {
return avm2_to_glow_filter(activation, object);
}
let gradient_bevel_filter = activation.avm2().classes().gradientbevelfilter;
let gradient_bevel_filter = activation
.avm2()
.classes()
.gradientbevelfilter
.inner_class_definition();
if object.is_of_type(gradient_bevel_filter, &mut activation.context) {
return Ok(Filter::GradientBevelFilter(avm2_to_gradient_filter(
activation, object,
)?));
}
let gradient_glow_filter = activation.avm2().classes().gradientglowfilter;
let gradient_glow_filter = activation
.avm2()
.classes()
.gradientglowfilter
.inner_class_definition();
if object.is_of_type(gradient_glow_filter, &mut activation.context) {
return Ok(Filter::GradientGlowFilter(avm2_to_gradient_filter(
activation, object,
)?));
}
let shader_filter = activation.avm2().classes().shaderfilter;
let shader_filter = activation
.avm2()
.classes()
.shaderfilter
.inner_class_definition();
if object.is_of_type(shader_filter, &mut activation.context) {
return Ok(Filter::ShaderFilter(avm2_to_shader_filter(
activation, object,

View File

@ -700,8 +700,12 @@ pub fn hit_test<'gc>(
);
let source_threshold = args.get_u32(activation, 1)?.clamp(0, u8::MAX.into()) as u8;
let compare_object = args.get_object(activation, 2, "secondObject")?;
let point_class = activation.avm2().classes().point;
let rectangle_class = activation.avm2().classes().rectangle;
let point_class = activation.avm2().classes().point.inner_class_definition();
let rectangle_class = activation
.avm2()
.classes()
.rectangle
.inner_class_definition();
if compare_object.is_of_type(point_class, &mut activation.context) {
let test_point = (

View File

@ -264,7 +264,9 @@ pub fn set_filters<'gc>(
Namespace::package("flash.filters", &mut activation.borrow_gc());
let filter_class = Multiname::new(filters_namespace, "BitmapFilter");
let filter_class_object = activation.resolve_class(&filter_class)?;
let filter_class_object = activation
.resolve_class(&filter_class)?
.inner_class_definition();
let mut filter_vec = Vec::with_capacity(filters_storage.length());
for filter in filters_storage.iter().flatten() {

View File

@ -176,7 +176,7 @@ pub fn add_child<'gc>(
if let Some(ctr) = parent.as_container() {
let child = args.get_object(activation, 0, "child")?;
if child.as_display_object().is_none() {
let sprite = activation.avm2().classes().sprite;
let sprite = activation.avm2().classes().sprite.inner_class_definition();
if child.is_of_type(sprite, &mut activation.context) {
// [NA] Hack to make Haxe work - they call addChild before super()
// This will create an empty sprite the same way sprite's constructor will.

View File

@ -504,7 +504,11 @@ pub fn set_texture_at<'gc>(
} else {
let obj = args[1].coerce_to_object(activation)?;
cube = obj.is_of_type(
activation.avm2().classes().cubetexture,
activation
.avm2()
.classes()
.cubetexture
.inner_class_definition(),
&mut activation.context,
);
Some(obj.as_texture().unwrap().handle())

View File

@ -235,13 +235,15 @@ impl<'gc> AvmSerializer<'gc> {
)?));
}
self.obj_stack.push(obj);
let value =
if obj.is_of_type(activation.avm2().classes().array, &mut activation.context) {
// TODO: Vectors
self.serialize_iterable(activation, obj)?
} else {
self.serialize_object(activation, obj)?
};
let value = if obj.is_of_type(
activation.avm2().classes().array.inner_class_definition(),
&mut activation.context,
) {
// TODO: Vectors
self.serialize_iterable(activation, obj)?
} else {
self.serialize_object(activation, obj)?
};
self.obj_stack
.pop()
.expect("Stack underflow during JSON serialization");

View File

@ -291,7 +291,7 @@ fn match_s<'gc>(
let this = Value::from(this).coerce_to_string(activation)?;
let regexp_class = activation.avm2().classes().regexp;
let pattern = if !pattern.is_of_type(activation, regexp_class) {
let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) {
let string = pattern.coerce_to_string(activation)?;
regexp_class.construct(activation, &[Value::String(string)])?
} else {
@ -401,7 +401,7 @@ fn search<'gc>(
let this = Value::from(this).coerce_to_string(activation)?;
let regexp_class = activation.avm2().classes().regexp;
let pattern = if !pattern.is_of_type(activation, regexp_class) {
let pattern = if !pattern.is_of_type(activation, regexp_class.inner_class_definition()) {
let string = pattern.coerce_to_string(activation)?;
regexp_class.construct(activation, &[Value::String(string)])?
} else {

View File

@ -77,7 +77,7 @@ fn class_call<'gc>(
while let Some(r) = iter.next(activation) {
let (_, item) = r?;
let coerced_item = item.coerce_to_type(activation, value_type)?;
let coerced_item = item.coerce_to_type(activation, value_type.inner_class_definition())?;
new_storage.push(coerced_item, activation)?;
}
@ -310,7 +310,7 @@ pub fn concat<'gc>(
let my_class = this
.instance_of()
.ok_or("TypeError: Tried to concat into a bare object")?;
let val_class = new_vector_storage.value_type();
let val_class = new_vector_storage.value_type().inner_class_definition();
for arg in args {
let arg_obj = arg
@ -319,7 +319,7 @@ 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) {
if !arg.is_of_type(activation, my_class.inner_class_definition()) {
return Err(format!(
"TypeError: Cannot coerce argument of type {:?} to argument of type {:?}",
arg_class.read().name(),
@ -344,7 +344,7 @@ pub fn concat<'gc>(
return Err(format!(
"TypeError: Cannot coerce Vector value of type {:?} to type {:?}",
other_val_class.read().name(),
val_class.inner_class_definition().read().name()
val_class.read().name()
)
.into());
}
@ -641,7 +641,8 @@ pub fn map<'gc>(
let (i, item) = r?;
let new_item = callback.call(receiver, &[item, i.into(), this.into()], activation)?;
let coerced_item = new_item.coerce_to_type(activation, value_type)?;
let coerced_item =
new_item.coerce_to_type(activation, value_type.inner_class_definition())?;
new_storage.push(coerced_item, activation)?;
}
@ -669,7 +670,7 @@ pub fn push<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
let value_type = vs.value_type();
let value_type = vs.value_type().inner_class_definition();
for arg in args {
let coerced_arg = arg.coerce_to_type(activation, value_type)?;
@ -703,7 +704,7 @@ pub fn unshift<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(mut vs) = this.as_vector_storage_mut(activation.context.gc_context) {
let value_type = vs.value_type();
let value_type = vs.value_type().inner_class_definition();
for arg in args.iter().rev() {
let coerced_arg = arg.coerce_to_type(activation, value_type)?;
@ -729,7 +730,7 @@ pub fn insert_at<'gc>(
.cloned()
.unwrap_or(Value::Undefined)
.coerce_to_i32(activation)?;
let value_type = vs.value_type();
let value_type = vs.value_type().inner_class_definition();
let value = args
.get(1)
.cloned()
@ -930,7 +931,7 @@ pub fn splice<'gc>(
let mut to_coerce = Vec::new();
for value in args[2..].iter() {
to_coerce.push(value.coerce_to_type(activation, value_type)?);
to_coerce.push(value.coerce_to_type(activation, value_type.inner_class_definition())?);
}
let new_vs =

View File

@ -1062,14 +1062,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// checking against this object.
fn is_of_type(
&self,
test_class: ClassObject<'gc>,
test_class: GcCell<'gc, Class<'gc>>,
context: &mut UpdateContext<'_, 'gc>,
) -> bool {
let my_class = self.instance_of();
// ES3 objects are not class instances but are still treated as
// instances of Object, which is an ES4 class.
if my_class.is_none() && Object::ptr_eq(test_class, context.avm2.classes().object) {
if my_class.is_none()
&& GcCell::ptr_eq(
test_class,
context.avm2.classes().object.inner_class_definition(),
)
{
true
} else if let Some(my_class) = my_class {
my_class.has_class_in_chain(test_class)

View File

@ -329,7 +329,11 @@ impl<'gc> ClassObject<'gc> {
let mut queue = vec![class];
while let Some(cls) = queue.pop() {
for interface_name in cls.read().direct_interfaces() {
let interface = self.early_resolve_class(scope.domain(), interface_name)?;
let interface = self.early_resolve_class(
scope.domain(),
interface_name,
activation.context.gc_context,
)?;
if !interface.read().is_interface() {
return Err(format!(
@ -346,7 +350,11 @@ impl<'gc> ClassObject<'gc> {
}
if let Some(superclass_name) = cls.read().super_class_name() {
queue.push(self.early_resolve_class(scope.domain(), superclass_name)?);
queue.push(self.early_resolve_class(
scope.domain(),
superclass_name,
activation.context.gc_context,
)?);
}
}
write.interfaces = interfaces;
@ -385,9 +393,10 @@ impl<'gc> ClassObject<'gc> {
&self,
domain: Domain<'gc>,
class_name: &Multiname<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<GcCell<'gc, Class<'gc>>, Error<'gc>> {
domain
.get_class(class_name)?
.get_class(class_name, mc)?
.ok_or_else(|| format!("Could not resolve class {class_name:?}").into())
}
@ -448,24 +457,19 @@ impl<'gc> ClassObject<'gc> {
/// interface we are checking against this class.
///
/// To test if a class *instance* is of a given type, see is_of_type.
pub fn has_class_in_chain(self, test_class: ClassObject<'gc>) -> bool {
pub fn has_class_in_chain(self, test_class: GcCell<'gc, Class<'gc>>) -> bool {
let mut my_class = Some(self);
while let Some(class) = my_class {
if Object::ptr_eq(class, test_class) {
if GcCell::ptr_eq(class.inner_class_definition(), test_class) {
return true;
}
if let (Some(my_param), Some(test_param)) =
(class.as_class_params(), test_class.as_class_params())
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())
{
let are_all_params_coercible = match (my_param, test_param) {
(Some(my_param), Some(test_param)) => my_param.has_class_in_chain(test_param),
(None, Some(_)) => false,
_ => true,
};
if are_all_params_coercible {
if my_param.has_class_in_chain(*other_single_param) {
return true;
}
}
@ -478,9 +482,9 @@ impl<'gc> ClassObject<'gc> {
// Therefore, we only need to check interfaces once, and we can skip
// checking them when we processing superclasses in the `while`
// further down in this method.
if test_class.inner_class_definition().read().is_interface() {
if test_class.read().is_interface() {
for interface in self.interfaces() {
if GcCell::ptr_eq(interface, test_class.inner_class_definition()) {
if GcCell::ptr_eq(interface, test_class) {
return true;
}
}
@ -851,7 +855,7 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
.get(0)
.cloned()
.unwrap_or(Value::Undefined)
.coerce_to_type(activation, self)
.coerce_to_type(activation, self.inner_class_definition())
}
}
@ -933,9 +937,8 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
.unwrap_or(activation.avm2().classes().object)
.inner_class_definition();
let parameterized_class = self_class
.read()
.with_type_param(class_param, activation.context.gc_context);
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;

View File

@ -133,7 +133,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
if name.contains_public_namespace() {
if let Some(name) = name.local_name() {
if let Ok(index) = name.parse::<usize>() {
let type_of = self.0.read().vector.value_type();
let type_of = self.0.read().vector.value_type().inner_class_definition();
let value = match value.coerce_to_type(activation, type_of)? {
Value::Undefined => self.0.read().vector.default(activation),
Value::Null => self.0.read().vector.default(activation),
@ -164,7 +164,7 @@ impl<'gc> TObject<'gc> for VectorObject<'gc> {
if name.contains_public_namespace() {
if let Some(name) = name.local_name() {
if let Ok(index) = name.parse::<usize>() {
let type_of = self.0.read().vector.value_type();
let type_of = self.0.read().vector.value_type().inner_class_definition();
let value = match value.coerce_to_type(activation, type_of)? {
Value::Undefined => self.0.read().vector.default(activation),
Value::Null => self.0.read().vector.default(activation),

View File

@ -1,15 +1,16 @@
//! Property data structures
use crate::avm2::object::TObject;
use crate::avm2::Activation;
use crate::avm2::ClassObject;
use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::TranslationUnit;
use crate::avm2::Value;
use gc_arena::GcCell;
use gc_arena::MutationContext;
use gc_arena::{Collect, Gc};
use super::class::Class;
#[derive(Debug, Collect, Clone, Copy)]
#[collect(no_drop)]
pub enum Property {
@ -39,7 +40,7 @@ pub enum PropertyClass<'gc> {
/// `Value::Undefined`, so it needs to be distinguished
/// from the `Object` class
Any,
Class(ClassObject<'gc>),
Class(GcCell<'gc, Class<'gc>>),
Name(Gc<'gc, (Multiname<'gc>, Option<TranslationUnit<'gc>>)>),
}
@ -64,56 +65,28 @@ impl<'gc> PropertyClass<'gc> {
PropertyClass::Class(class) => (Some(*class), false),
PropertyClass::Name(gc) => {
let (name, unit) = &**gc;
let outcome = resolve_class_private(name, *unit, activation)?;
let class = match outcome {
ResolveOutcome::Class(class) => {
if name.is_any_name() {
*self = PropertyClass::Any;
(None, true)
} else {
// Note - we look up the class in the domain by name, which allows us to look up private classes.
// This also has the advantage of letting us coerce to a class while the `ClassObject`
// is still being constructed (since the `Class` will already exist in the domain).
// We should only be missing a translation unit when performing a lookup from playerglobals,
// so use that domain if we don't have a translation unit.
let domain =
unit.map_or(activation.avm2().playerglobals_domain, |u| u.domain());
if let Some(class) = domain.get_class(name, activation.context.gc_context)? {
*self = PropertyClass::Class(class);
(Some(class), true)
} else {
return Err(format!(
"Could not resolve class {name:?} for property coercion"
)
.into());
}
ResolveOutcome::Any => {
*self = PropertyClass::Any;
(None, true)
}
ResolveOutcome::NotFound => {
// FP allows a class to reference its own type in a static initializer:
// `class Foo { static var INSTANCE: Foo = new Foo(); }`
// When this happens, the `ClassObject` for `Foo` will not yet
// be available when we perform the coercion, since we're still
// in the process of constructing it.
//
// Fortunately, a coercion to a non-primitive class either
// succeeds with the value unchanged, or fails (if the object
// is not an instance of the class). Therefore, we can just check
// if the class name and domain match our property name, without
// actually needing to perform resolution. This does not handle subclasses,
// but that's fine - a superclass cannot reference a subclass from a class
// initializer.
//
// We should eventually be able to remove this when we refactor
// `Class`/`ClassObject` to be closer to what avmplus does.
if let Some(object) = value.as_object() {
if let Some(class) = object.instance_of() {
if name.contains_name(&class.inner_class_definition().read().name())
&& unit.map(|u| u.domain())
== Some(class.class_scope().domain())
{
// Even though resolution succeeded, we haven't modified
// this `PropertyClass`, so return `false`
return Ok((value, false));
}
}
} else if matches!(value, Value::Null) || matches!(value, Value::Undefined)
{
//AVM2 properties are nullable, so null is always an instance of the class
return Ok((Value::Null, false));
}
return Err(Error::from(format!(
"Attempted to perform (private) resolution of nonexistent type {name:?}",
)));
}
};
class
}
}
PropertyClass::Any => (None, false),
};
@ -129,58 +102,13 @@ impl<'gc> PropertyClass<'gc> {
pub fn get_name(&self, mc: MutationContext<'gc, '_>) -> Multiname<'gc> {
match self {
PropertyClass::Class(class) => class.inner_class_definition().read().name().into(),
PropertyClass::Class(class) => class.read().name().into(),
PropertyClass::Name(gc) => gc.0.clone(),
PropertyClass::Any => Multiname::any(mc),
}
}
}
enum ResolveOutcome<'gc> {
Class(ClassObject<'gc>),
Any,
NotFound,
}
/// Resolves a class definition referenced by the type of a property.
/// This supports private (`Namespace::Private`) classes,
/// and does not use the `ScopeStack`/`ScopeChain`.
///
/// This is an internal operation used to resolve property type names.
/// It does not correspond to any opcode or native method.
fn resolve_class_private<'gc>(
name: &Multiname<'gc>,
unit: Option<TranslationUnit<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<ResolveOutcome<'gc>, Error<'gc>> {
// A Property may have a type of '*' (which corresponds to 'Multiname::any()')
// We don't want to perform any coercions in this case - in particular,
// this means that the property can have a value of `Undefined`.
// If the type is `Object`, then a value of `Undefind` gets coerced
// to `Null`
if name.is_any_name() {
Ok(ResolveOutcome::Any)
} else {
// First, check the domain for an exported (non-private) class.
// If the property we're resolving for lacks a `TranslationUnit`,
// use the stage domain
let domain = unit.map_or(activation.avm2().stage_domain, |u| u.domain());
let globals = if let Some((_, mut script)) = domain.get_defining_script(name)? {
script.globals(&mut activation.context)?
} else if unit.is_some() {
return Err(format!("Could not find script for class trait {name:?}").into());
} else {
return Err(format!("Missing script and translation unit for class {name:?}").into());
};
Ok(globals
.get_property(name, activation)?
.as_object()
.and_then(|o| o.as_class_object())
.map_or(ResolveOutcome::NotFound, ResolveOutcome::Class))
}
}
impl Property {
pub fn new_method(disp_id: u32) -> Self {
Property::Method { disp_id }

View File

@ -3,7 +3,7 @@
use crate::avm2::activation::Activation;
use crate::avm2::error;
use crate::avm2::error::type_error;
use crate::avm2::object::{ClassObject, NamespaceObject, Object, PrimitiveObject, TObject};
use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject};
use crate::avm2::script::TranslationUnit;
use crate::avm2::Error;
use crate::avm2::Multiname;
@ -16,6 +16,7 @@ use std::cell::Ref;
use std::mem::size_of;
use swf::avm2::types::{DefaultValue as AbcDefaultValue, Index};
use super::class::Class;
use super::e4x::E4XNode;
/// Indicate what kind of primitive coercion would be preferred when coercing
@ -957,44 +958,19 @@ impl<'gc> Value<'gc> {
activation: &mut Activation<'_, 'gc>,
type_name: &Multiname<'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
let param_type = match activation.resolve_type(type_name) {
Ok(param_type) => param_type,
Err(e) => {
// While running a class initializer, we might need to resolve the class
// itself. For example, a static/const can run a static method, which does
// `var foo:ClassBeingInitialized = new ClassBeingInitialized();`
//
// Since the class initializer is running, we won't be able to resolve the
// ClassObject yet. If the normal `resolve_type` lookup fails, then
// try resolving the class through `domain().get_class`, and check if the
// object's class matches directly (not considering superclasses or interfaces).
// Any superclasses or superinterfaces will already have been initialized,
// so the `resolve_type` lookup will succeed for them.
if let Ok(Some(resolved_class)) = activation.domain().get_class(type_name) {
// Note that we do this check *after* successfully resolving the class. This ensures
// that we still produce errors when trying to coerce null/undefined to a completely
// non-existent class.
if matches!(self, Value::Undefined) || matches!(self, Value::Null) {
return Ok(Value::Null);
}
if let Some(obj) = self.as_object() {
if let Some(obj_class) = obj.instance_of_class_definition() {
if GcCell::ptr_eq(resolved_class, obj_class) {
return Ok(*self);
}
}
}
}
return Err(e);
}
};
if let Some(param_type) = param_type {
self.coerce_to_type(activation, param_type)
} else {
Ok(*self)
if type_name.is_any_name() {
return Ok(*self);
}
let param_type = activation
.domain()
.get_class(type_name, activation.context.gc_context)?
.ok_or_else(|| {
Error::RustError(
format!("Failed to lookup class {:?} during coercion", type_name).into(),
)
})?;
self.coerce_to_type(activation, param_type)
}
/// Coerce the value to another value by type name.
@ -1008,21 +984,33 @@ impl<'gc> Value<'gc> {
pub fn coerce_to_type(
&self,
activation: &mut Activation<'_, 'gc>,
class: ClassObject<'gc>,
class: GcCell<'gc, Class<'gc>>,
) -> Result<Value<'gc>, Error<'gc>> {
if Object::ptr_eq(class, activation.avm2().classes().int) {
if GcCell::ptr_eq(
class,
activation.avm2().classes().int.inner_class_definition(),
) {
return Ok(self.coerce_to_i32(activation)?.into());
}
if Object::ptr_eq(class, activation.avm2().classes().uint) {
if GcCell::ptr_eq(
class,
activation.avm2().classes().uint.inner_class_definition(),
) {
return Ok(self.coerce_to_u32(activation)?.into());
}
if Object::ptr_eq(class, activation.avm2().classes().number) {
if GcCell::ptr_eq(
class,
activation.avm2().classes().number.inner_class_definition(),
) {
return Ok(self.coerce_to_number(activation)?.into());
}
if Object::ptr_eq(class, activation.avm2().classes().boolean) {
if GcCell::ptr_eq(
class,
activation.avm2().classes().boolean.inner_class_definition(),
) {
return Ok(self.coerce_to_boolean().into());
}
@ -1030,7 +1018,10 @@ impl<'gc> Value<'gc> {
return Ok(Value::Null);
}
if Object::ptr_eq(class, activation.avm2().classes().string) {
if GcCell::ptr_eq(
class,
activation.avm2().classes().string.inner_class_definition(),
) {
return Ok(self.coerce_to_string(activation)?.into());
}
@ -1040,7 +1031,7 @@ impl<'gc> Value<'gc> {
}
if let Some(vector) = object.as_vector_storage() {
let name = class.inner_class_definition().read().name();
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")
@ -1059,7 +1050,6 @@ impl<'gc> Value<'gc> {
}
let name = class
.inner_class_definition()
.read()
.name()
.to_qualified_name_err_message(activation.context.gc_context);
@ -1128,15 +1118,24 @@ impl<'gc> Value<'gc> {
pub fn is_of_type(
&self,
activation: &mut Activation<'_, 'gc>,
type_object: ClassObject<'gc>,
type_object: GcCell<'gc, Class<'gc>>,
) -> bool {
if Object::ptr_eq(type_object, activation.avm2().classes().number) {
if GcCell::ptr_eq(
type_object,
activation.avm2().classes().number.inner_class_definition(),
) {
return self.is_number();
}
if Object::ptr_eq(type_object, activation.avm2().classes().uint) {
if GcCell::ptr_eq(
type_object,
activation.avm2().classes().uint.inner_class_definition(),
) {
return self.is_u32();
}
if Object::ptr_eq(type_object, activation.avm2().classes().int) {
if GcCell::ptr_eq(
type_object,
activation.avm2().classes().int.inner_class_definition(),
) {
return self.is_i32();
}

View File

@ -407,7 +407,13 @@ impl<'gc> VTable<'gc> {
),
TraitKind::Function { .. } => (
Property::new_slot(new_slot_id),
PropertyClass::Class(activation.avm2().classes().function),
PropertyClass::Class(
activation
.avm2()
.classes()
.function
.inner_class_definition(),
),
),
TraitKind::Const {
type_name, unit, ..
@ -421,7 +427,9 @@ impl<'gc> VTable<'gc> {
),
TraitKind::Class { .. } => (
Property::new_const_slot(new_slot_id),
PropertyClass::Class(activation.avm2().classes().class),
PropertyClass::Class(
activation.avm2().classes().class.inner_class_definition(),
),
),
_ => unreachable!(),
};
@ -486,7 +494,9 @@ impl<'gc> VTable<'gc> {
write
.resolved_traits
.insert(name, Property::new_slot(new_slot_id));
write.slot_classes.push(PropertyClass::Class(class));
write
.slot_classes
.push(PropertyClass::Class(class.inner_class_definition()));
new_slot_id
}

View File

@ -1708,7 +1708,7 @@ pub trait TDisplayObject<'gc>:
.object2()
.as_object()
.expect("MovieClip object should have been constructed");
let movieclip_class = context.avm2.classes().movieclip;
let movieclip_class = context.avm2.classes().movieclip.inner_class_definition();
// It's possible to have a DefineSprite tag with multiple frames, but have
// the corresponding `SymbolClass` *not* extend `MovieClip` (e.g. extending `Sprite` directly.)
// When this occurs, Flash Player will run the first frame, and immediately stop.

View File

@ -219,9 +219,13 @@ impl<'gc> Bitmap<'gc> {
context: &mut UpdateContext<'_, 'gc>,
class: Avm2ClassObject<'gc>,
) {
let bitmap_class = if class.has_class_in_chain(context.avm2.classes().bitmap) {
let bitmap_class = if class
.has_class_in_chain(context.avm2.classes().bitmap.inner_class_definition())
{
BitmapClass::Bitmap(class)
} else if class.has_class_in_chain(context.avm2.classes().bitmapdata) {
} else if class
.has_class_in_chain(context.avm2.classes().bitmapdata.inner_class_definition())
{
BitmapClass::BitmapData(class)
} else {
return tracing::error!("Associated class {:?} for symbol {} must extend flash.display.Bitmap or BitmapData, does neither", class.inner_class_definition().read().name(), self.id());

View File

@ -171,4 +171,16 @@ trace("/// c_vector[1] = b_vector;");
c_vector[1] = b_vector;
trace("/// (contents of c_vector...)");
trace_vector(c_vector);
trace_vector(c_vector);
class MyObject {}
var myobj_vec: Vector.<MyObject> = new <MyObject>[];
try {
var cast: Vector.<int> = myobj_vec;
} catch (e) {
// Replace the non-deterministic address value with a placeholder string.
var normalized = e.toString().replace(/@[0-9A-Fa-f]+/, "@ADDRESS")
trace("Caught error: " + normalized);
}

View File

@ -63,3 +63,4 @@ true
/// (contents of c_vector...)
1,2
5,16
Caught error: TypeError: Error #1034: Type Coercion failed: cannot convert __AS3__.vec::Vector.<Test.as$38::MyObject>@ADDRESS to __AS3__.vec.Vector.<int>.