2020-06-30 03:58:48 +00:00
|
|
|
//! AVM2 classes
|
|
|
|
|
2020-07-03 01:49:53 +00:00
|
|
|
use crate::avm2::method::Method;
|
2020-06-30 03:58:48 +00:00
|
|
|
use crate::avm2::names::{Multiname, Namespace, QName};
|
2020-07-01 03:44:14 +00:00
|
|
|
use crate::avm2::r#trait::{Trait, TraitKind};
|
2020-07-02 03:21:30 +00:00
|
|
|
use crate::avm2::script::TranslationUnit;
|
2020-07-18 20:20:58 +00:00
|
|
|
use crate::avm2::string::AvmString;
|
2020-07-29 03:08:05 +00:00
|
|
|
use crate::avm2::{Avm2, Error};
|
2020-07-06 21:43:00 +00:00
|
|
|
use enumset::{EnumSet, EnumSetType};
|
2020-07-02 03:21:30 +00:00
|
|
|
use gc_arena::{Collect, GcCell, MutationContext};
|
2020-07-03 01:19:31 +00:00
|
|
|
use swf::avm2::types::{Class as AbcClass, Instance as AbcInstance};
|
2020-06-30 03:58:48 +00:00
|
|
|
|
2020-07-06 21:43:00 +00:00
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(require_static)]
|
|
|
|
pub struct CollectWrapper<T>(T);
|
|
|
|
|
|
|
|
/// All possible attributes for a given class.
|
|
|
|
#[derive(EnumSetType, Debug)]
|
|
|
|
pub enum ClassAttributes {
|
|
|
|
/// Class is sealed, attempts to set or init dynamic properties on an
|
|
|
|
/// object will generate a runtime error.
|
|
|
|
Sealed,
|
|
|
|
|
|
|
|
/// Class is final, attempts to construct child classes from it will
|
|
|
|
/// generate a verification error.
|
|
|
|
Final,
|
|
|
|
|
|
|
|
/// Class is an interface.
|
|
|
|
Interface,
|
|
|
|
}
|
|
|
|
|
2020-06-30 03:58:48 +00:00
|
|
|
/// A loaded ABC Class which can be used to construct objects with.
|
|
|
|
#[derive(Clone, Debug, Collect)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Class<'gc> {
|
|
|
|
/// The name of the class.
|
2020-07-14 02:21:18 +00:00
|
|
|
name: QName<'gc>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// The name of this class's superclass.
|
2020-07-14 02:21:18 +00:00
|
|
|
super_class: Option<Multiname<'gc>>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
2020-07-06 21:43:00 +00:00
|
|
|
/// Attributes of the given class.
|
|
|
|
attributes: CollectWrapper<EnumSet<ClassAttributes>>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// The namespace that protected traits of this class are stored into.
|
2020-07-14 02:21:18 +00:00
|
|
|
protected_namespace: Option<Namespace<'gc>>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// The list of interfaces this class implements.
|
2020-07-14 02:21:18 +00:00
|
|
|
interfaces: Vec<Multiname<'gc>>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// The instance initializer for this class.
|
|
|
|
///
|
|
|
|
/// Must be called each time a new class instance is constructed.
|
2020-07-01 03:44:14 +00:00
|
|
|
instance_init: Method<'gc>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// Instance traits for a given class.
|
|
|
|
///
|
|
|
|
/// These are accessed as normal instance properties; they should not be
|
|
|
|
/// present on prototypes, but instead should shadow any prototype
|
|
|
|
/// properties that would match.
|
2020-07-02 03:21:30 +00:00
|
|
|
instance_traits: Vec<Trait<'gc>>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// The class initializer for this class.
|
|
|
|
///
|
|
|
|
/// Must be called once prior to any use of this class.
|
2020-07-01 03:44:14 +00:00
|
|
|
class_init: Method<'gc>,
|
2020-06-30 03:58:48 +00:00
|
|
|
|
|
|
|
/// Static traits for a given class.
|
|
|
|
///
|
|
|
|
/// These are accessed as constructor properties.
|
2020-07-02 03:21:30 +00:00
|
|
|
class_traits: Vec<Trait<'gc>>,
|
|
|
|
|
|
|
|
/// Whether or not this `Class` has loaded it's traits or not.
|
|
|
|
traits_loaded: bool,
|
2020-07-01 03:44:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Find traits in a list of traits matching a name.
|
|
|
|
///
|
|
|
|
/// This function also enforces final/override bits on the traits, and will
|
|
|
|
/// raise `VerifyError`s as needed.
|
|
|
|
///
|
|
|
|
/// TODO: This is an O(n^2) algorithm, it sucks.
|
|
|
|
fn do_trait_lookup<'gc>(
|
2020-07-14 02:21:18 +00:00
|
|
|
name: &QName<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
known_traits: &mut Vec<Trait<'gc>>,
|
|
|
|
all_traits: &[Trait<'gc>],
|
2020-07-01 03:44:14 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
for trait_entry in all_traits {
|
|
|
|
if name == trait_entry.name() {
|
|
|
|
for known_trait in known_traits.iter() {
|
|
|
|
match (&trait_entry.kind(), &known_trait.kind()) {
|
|
|
|
(TraitKind::Getter { .. }, TraitKind::Setter { .. }) => continue,
|
|
|
|
(TraitKind::Setter { .. }, TraitKind::Getter { .. }) => continue,
|
|
|
|
_ => {}
|
|
|
|
};
|
|
|
|
|
|
|
|
if known_trait.is_final() {
|
|
|
|
return Err("Attempting to override a final definition".into());
|
|
|
|
}
|
|
|
|
|
|
|
|
if !trait_entry.is_override() {
|
|
|
|
return Err("Definition override is not marked as override".into());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
known_traits.push(trait_entry.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'gc> Class<'gc> {
|
2020-07-05 03:13:13 +00:00
|
|
|
/// Create a new class.
|
|
|
|
///
|
|
|
|
/// This function is primarily intended for use by native code to define
|
|
|
|
/// builtin classes. The absolute minimum necessary to define a class is
|
|
|
|
/// required here; further methods allow further changes to the class.
|
|
|
|
///
|
|
|
|
/// Classes created in this way cannot have traits loaded from an ABC file
|
|
|
|
/// using `load_traits`.
|
|
|
|
pub fn new(
|
2020-07-06 21:43:00 +00:00
|
|
|
name: QName<'gc>,
|
2020-07-07 04:18:52 +00:00
|
|
|
super_class: Option<Multiname<'gc>>,
|
2020-07-05 03:13:13 +00:00
|
|
|
instance_init: Method<'gc>,
|
|
|
|
class_init: Method<'gc>,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> GcCell<'gc, Self> {
|
|
|
|
GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
Self {
|
|
|
|
name,
|
2020-07-07 04:18:52 +00:00
|
|
|
super_class,
|
2020-07-06 21:43:00 +00:00
|
|
|
attributes: CollectWrapper(EnumSet::empty()),
|
2020-07-05 03:13:13 +00:00
|
|
|
protected_namespace: None,
|
|
|
|
interfaces: Vec::new(),
|
|
|
|
instance_init,
|
|
|
|
instance_traits: Vec::new(),
|
|
|
|
class_init,
|
|
|
|
class_traits: Vec::new(),
|
|
|
|
traits_loaded: true,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-07-06 21:43:30 +00:00
|
|
|
/// Set the attributes of the class (sealed/final/interface status).
|
|
|
|
pub fn set_attributes(&mut self, attributes: EnumSet<ClassAttributes>) {
|
|
|
|
self.attributes = CollectWrapper(attributes);
|
|
|
|
}
|
|
|
|
|
2020-07-02 03:21:30 +00:00
|
|
|
/// Construct a class from a `TranslationUnit` and it's class index.
|
|
|
|
///
|
|
|
|
/// The returned class will be allocated, but no traits will be loaded. The
|
|
|
|
/// caller is responsible for storing the class in the `TranslationUnit`
|
|
|
|
/// and calling `load_traits` to complete the trait-loading process.
|
|
|
|
pub fn from_abc_index(
|
2020-07-02 22:48:37 +00:00
|
|
|
unit: TranslationUnit<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
class_index: u32,
|
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<GcCell<'gc, Self>, Error> {
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc = unit.abc();
|
|
|
|
let abc_class: Result<&AbcClass, Error> = abc
|
2020-07-02 03:21:30 +00:00
|
|
|
.classes
|
|
|
|
.get(class_index as usize)
|
2020-07-03 01:11:10 +00:00
|
|
|
.ok_or_else(|| "LoadError: Class index not valid".into());
|
2020-07-02 03:21:30 +00:00
|
|
|
let abc_class = abc_class?;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc_instance: Result<&AbcInstance, Error> = abc
|
2020-07-02 03:21:30 +00:00
|
|
|
.instances
|
|
|
|
.get(class_index as usize)
|
2020-07-03 01:11:10 +00:00
|
|
|
.ok_or_else(|| "LoadError: Instance index not valid".into());
|
2020-07-02 03:21:30 +00:00
|
|
|
let abc_instance = abc_instance?;
|
|
|
|
|
2020-07-15 00:05:25 +00:00
|
|
|
let name = QName::from_abc_multiname(unit, abc_instance.name.clone(), mc)?;
|
2020-07-02 03:21:30 +00:00
|
|
|
let super_class = if abc_instance.super_name.0 == 0 {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(Multiname::from_abc_multiname_static(
|
2020-07-15 00:05:25 +00:00
|
|
|
unit,
|
2020-07-02 23:51:32 +00:00
|
|
|
abc_instance.super_name.clone(),
|
2020-07-14 02:21:18 +00:00
|
|
|
mc,
|
2020-07-02 03:21:30 +00:00
|
|
|
)?)
|
|
|
|
};
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let protected_namespace = if let Some(ns) = &abc_instance.protected_namespace {
|
2020-07-15 00:05:25 +00:00
|
|
|
Some(Namespace::from_abc_namespace(unit, ns.clone(), mc)?)
|
2020-07-02 03:21:30 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut interfaces = Vec::new();
|
2020-07-02 23:51:32 +00:00
|
|
|
for interface_name in abc_instance.interfaces.iter() {
|
2020-07-02 03:21:30 +00:00
|
|
|
interfaces.push(Multiname::from_abc_multiname_static(
|
2020-07-15 00:05:25 +00:00
|
|
|
unit,
|
2020-07-02 23:51:32 +00:00
|
|
|
interface_name.clone(),
|
2020-07-14 02:21:18 +00:00
|
|
|
mc,
|
2020-07-02 03:21:30 +00:00
|
|
|
)?);
|
|
|
|
}
|
|
|
|
|
2020-07-02 22:48:37 +00:00
|
|
|
let instance_init = unit.load_method(abc_instance.init_method.0, mc)?;
|
|
|
|
let class_init = unit.load_method(abc_class.init_method.0, mc)?;
|
2020-07-02 03:21:30 +00:00
|
|
|
|
2020-07-06 21:43:00 +00:00
|
|
|
let mut attributes = EnumSet::new();
|
|
|
|
if abc_instance.is_sealed {
|
|
|
|
attributes |= ClassAttributes::Sealed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if abc_instance.is_final {
|
|
|
|
attributes |= ClassAttributes::Final;
|
|
|
|
}
|
|
|
|
|
|
|
|
if abc_instance.is_interface {
|
|
|
|
attributes |= ClassAttributes::Interface;
|
|
|
|
}
|
|
|
|
|
2020-07-02 03:21:30 +00:00
|
|
|
Ok(GcCell::allocate(
|
|
|
|
mc,
|
|
|
|
Self {
|
|
|
|
name,
|
|
|
|
super_class,
|
2020-07-06 21:43:00 +00:00
|
|
|
attributes: CollectWrapper(attributes),
|
2020-07-02 03:21:30 +00:00
|
|
|
protected_namespace,
|
|
|
|
interfaces,
|
|
|
|
instance_init,
|
|
|
|
instance_traits: Vec::new(),
|
|
|
|
class_init,
|
|
|
|
class_traits: Vec::new(),
|
|
|
|
traits_loaded: false,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Finish the class-loading process by loading traits.
|
|
|
|
///
|
|
|
|
/// This process must be done after the `Class` has been stored in the
|
|
|
|
/// `TranslationUnit`. Failing to do so runs the risk of runaway recursion
|
|
|
|
/// or double-borrows. It should be done before the class is actually
|
|
|
|
/// instantiated into an `Object`.
|
|
|
|
pub fn load_traits(
|
|
|
|
&mut self,
|
2020-07-02 22:48:37 +00:00
|
|
|
unit: TranslationUnit<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
class_index: u32,
|
2020-07-29 03:08:05 +00:00
|
|
|
avm2: &mut Avm2<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
mc: MutationContext<'gc, '_>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
if self.traits_loaded {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.traits_loaded = true;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc = unit.abc();
|
|
|
|
let abc_class: Result<&AbcClass, Error> = abc
|
2020-07-02 03:21:30 +00:00
|
|
|
.classes
|
|
|
|
.get(class_index as usize)
|
|
|
|
.ok_or_else(|| "LoadError: Class index not valid".into());
|
|
|
|
let abc_class = abc_class?;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
let abc_instance: Result<&AbcInstance, Error> = abc
|
2020-07-02 03:21:30 +00:00
|
|
|
.instances
|
|
|
|
.get(class_index as usize)
|
|
|
|
.ok_or_else(|| "LoadError: Instance index not valid".into());
|
|
|
|
let abc_instance = abc_instance?;
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
for abc_trait in abc_instance.traits.iter() {
|
2020-07-02 03:21:30 +00:00
|
|
|
self.instance_traits
|
2020-07-29 03:08:05 +00:00
|
|
|
.push(Trait::from_abc_trait(unit, &abc_trait, avm2, mc)?);
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
2020-07-02 23:51:32 +00:00
|
|
|
for abc_trait in abc_class.traits.iter() {
|
2020-07-02 03:21:30 +00:00
|
|
|
self.class_traits
|
2020-07-29 03:08:05 +00:00
|
|
|
.push(Trait::from_abc_trait(unit, &abc_trait, avm2, mc)?);
|
2020-07-02 03:21:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn name(&self) -> &QName<'gc> {
|
2020-07-01 03:44:14 +00:00
|
|
|
&self.name
|
|
|
|
}
|
|
|
|
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn super_class_name(&self) -> &Option<Multiname<'gc>> {
|
2020-07-01 03:44:14 +00:00
|
|
|
&self.super_class
|
|
|
|
}
|
|
|
|
|
2020-07-05 03:13:13 +00:00
|
|
|
/// Define a trait on the class.
|
|
|
|
///
|
|
|
|
/// Class traits will be accessible as properties on the class constructor
|
|
|
|
/// function.
|
|
|
|
pub fn define_class_trait(&mut self, my_trait: Trait<'gc>) {
|
|
|
|
self.class_traits.push(my_trait);
|
|
|
|
}
|
|
|
|
|
2020-07-01 03:44:14 +00:00
|
|
|
/// Given a name, append class traits matching the name to a list of known
|
|
|
|
/// traits.
|
|
|
|
///
|
|
|
|
/// This function adds it's result onto the list of known traits, with the
|
|
|
|
/// caveat that duplicate entries will be replaced (if allowed). As such, this
|
|
|
|
/// function should be run on the class hierarchy from top to bottom.
|
|
|
|
///
|
|
|
|
/// If a given trait has an invalid name, attempts to override a final trait,
|
|
|
|
/// or overlaps an existing trait without being an override, then this function
|
|
|
|
/// returns an error.
|
|
|
|
pub fn lookup_class_traits(
|
|
|
|
&self,
|
2020-07-14 02:21:18 +00:00
|
|
|
name: &QName<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
known_traits: &mut Vec<Trait<'gc>>,
|
2020-07-01 03:44:14 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
do_trait_lookup(name, known_traits, &self.class_traits)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines if this class provides a given trait on itself.
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn has_class_trait(&self, name: &QName<'gc>) -> bool {
|
2020-07-01 03:44:14 +00:00
|
|
|
for trait_entry in self.class_traits.iter() {
|
|
|
|
if name == trait_entry.name() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Look for a class trait with a given local name, and return it's
|
|
|
|
/// namespace.
|
|
|
|
///
|
|
|
|
/// TODO: Matching multiple namespaces with the same local name is at least
|
|
|
|
/// claimed by the AVM2 specification to be a `VerifyError`.
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn resolve_any_class_trait(&self, local_name: AvmString<'gc>) -> Option<Namespace<'gc>> {
|
2020-07-01 03:44:14 +00:00
|
|
|
for trait_entry in self.class_traits.iter() {
|
|
|
|
if local_name == trait_entry.name().local_name() {
|
|
|
|
return Some(trait_entry.name().namespace().clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-07-05 03:13:13 +00:00
|
|
|
/// Define a trait on instances of the class.
|
|
|
|
///
|
|
|
|
/// Instance traits will be accessible as properties on instances of the
|
|
|
|
/// class. They will not be accessible on the class prototype, and any
|
|
|
|
/// properties defined on the prototype will be shadowed by these traits.
|
|
|
|
pub fn define_instance_trait(&mut self, my_trait: Trait<'gc>) {
|
|
|
|
self.class_traits.push(my_trait);
|
|
|
|
}
|
|
|
|
|
2020-07-01 03:44:14 +00:00
|
|
|
/// Given a name, append instance traits matching the name to a list of
|
|
|
|
/// known traits.
|
|
|
|
///
|
|
|
|
/// This function adds it's result onto the list of known traits, with the
|
|
|
|
/// caveat that duplicate entries will be replaced (if allowed). As such, this
|
|
|
|
/// function should be run on the class hierarchy from top to bottom.
|
|
|
|
///
|
|
|
|
/// If a given trait has an invalid name, attempts to override a final trait,
|
|
|
|
/// or overlaps an existing trait without being an override, then this function
|
|
|
|
/// returns an error.
|
|
|
|
pub fn lookup_instance_traits(
|
|
|
|
&self,
|
2020-07-14 02:21:18 +00:00
|
|
|
name: &QName<'gc>,
|
2020-07-02 03:21:30 +00:00
|
|
|
known_traits: &mut Vec<Trait<'gc>>,
|
2020-07-01 03:44:14 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
do_trait_lookup(name, known_traits, &self.instance_traits)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Determines if this class provides a given trait on it's instances.
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn has_instance_trait(&self, name: &QName<'gc>) -> bool {
|
2020-07-01 03:44:14 +00:00
|
|
|
for trait_entry in self.instance_traits.iter() {
|
|
|
|
if name == trait_entry.name() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Look for an instance trait with a given local name, and return it's
|
|
|
|
/// namespace.
|
|
|
|
///
|
|
|
|
/// TODO: Matching multiple namespaces with the same local name is at least
|
|
|
|
/// claimed by the AVM2 specification to be a `VerifyError`.
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn resolve_any_instance_trait(&self, local_name: AvmString<'gc>) -> Option<Namespace<'gc>> {
|
2020-07-01 03:44:14 +00:00
|
|
|
for trait_entry in self.instance_traits.iter() {
|
|
|
|
if local_name == trait_entry.name().local_name() {
|
|
|
|
return Some(trait_entry.name().namespace().clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get this class's instance initializer.
|
|
|
|
pub fn instance_init(&self) -> Method<'gc> {
|
2020-07-02 23:51:32 +00:00
|
|
|
self.instance_init.clone()
|
2020-07-01 03:44:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get this class's class initializer.
|
|
|
|
pub fn class_init(&self) -> Method<'gc> {
|
2020-07-02 23:51:32 +00:00
|
|
|
self.class_init.clone()
|
2020-07-01 03:44:14 +00:00
|
|
|
}
|
2020-07-08 03:52:29 +00:00
|
|
|
|
2020-07-14 02:21:18 +00:00
|
|
|
pub fn interfaces(&self) -> &[Multiname<'gc>] {
|
2020-07-08 03:52:29 +00:00
|
|
|
&self.interfaces
|
|
|
|
}
|
2020-06-30 03:58:48 +00:00
|
|
|
}
|