Decouple the entire trait machinery from ABC-provided traits.

This commit breaks the build: we still need to tell `Avm2` how to turn ABC traits into our own internal `Trait<'gc>`, `Class<'gc>`, and `Method<'gc>` types. We also need something to track which traits have already been instantiated, because `callstatic` would otherwise reinstantiate the trait in a different scope. (In fact, I think it *does* do exactly that right now...)
This commit is contained in:
David Wendt 2020-06-30 23:44:14 -04:00
parent 15a62d31cb
commit 70e9030072
6 changed files with 381 additions and 309 deletions

View File

@ -1,6 +1,6 @@
//! Activation frames //! Activation frames
use crate::avm2::function::{Avm2Function, Avm2MethodEntry}; use crate::avm2::function::Avm2MethodEntry;
use crate::avm2::object::Object; use crate::avm2::object::Object;
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::script_object::ScriptObject; use crate::avm2::script_object::ScriptObject;
@ -169,13 +169,12 @@ impl<'gc> Activation<'gc> {
pub fn from_action( pub fn from_action(
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
action: &Avm2Function<'gc>, method: Avm2MethodEntry,
scope: Option<GcCell<'gc, Scope<'gc>>>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
arguments: &[Value<'gc>], arguments: &[Value<'gc>],
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let method = action.method.clone();
let scope = action.scope;
let num_locals = method.body().num_locals; let num_locals = method.body().num_locals;
let num_declared_arguments = method.method().params.len() as u32; let num_declared_arguments = method.method().params.len() as u32;
let local_registers = GcCell::allocate( let local_registers = GcCell::allocate(

View File

@ -1,9 +1,10 @@
//! AVM2 classes //! AVM2 classes
use crate::avm2::function::Executable; use crate::avm2::function::Method;
use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::r#trait::Trait; use crate::avm2::r#trait::{Trait, TraitKind};
use gc_arena::Collect; use crate::avm2::Error;
use gc_arena::{Collect, Gc};
use std::rc::Rc; use std::rc::Rc;
use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance}; use swf::avm2::types::{AbcFile, Class as AbcClass, Index, Instance as AbcInstance};
@ -88,22 +89,166 @@ pub struct Class<'gc> {
/// The instance initializer for this class. /// The instance initializer for this class.
/// ///
/// Must be called each time a new class instance is constructed. /// Must be called each time a new class instance is constructed.
instance_initializer: Executable<'gc>, instance_init: Method<'gc>,
/// Instance traits for a given class. /// Instance traits for a given class.
/// ///
/// These are accessed as normal instance properties; they should not be /// These are accessed as normal instance properties; they should not be
/// present on prototypes, but instead should shadow any prototype /// present on prototypes, but instead should shadow any prototype
/// properties that would match. /// properties that would match.
instance_traits: Vec<Trait<'gc>>, instance_traits: Vec<Gc<'gc, Trait<'gc>>>,
/// The class initializer for this class. /// The class initializer for this class.
/// ///
/// Must be called once prior to any use of this class. /// Must be called once prior to any use of this class.
class_init: Executable<'gc>, class_init: Method<'gc>,
/// Static traits for a given class. /// Static traits for a given class.
/// ///
/// These are accessed as constructor properties. /// These are accessed as constructor properties.
class_traits: Vec<Trait<'gc>>, class_traits: Vec<Gc<'gc, Trait<'gc>>>,
}
/// 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>(
name: &QName,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
all_traits: &[Gc<'gc, Trait<'gc>>],
) -> 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> {
pub fn name(&self) -> &QName {
&self.name
}
pub fn super_class_name(&self) -> &Option<Multiname> {
&self.super_class
}
/// 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,
name: &QName,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> {
do_trait_lookup(name, known_traits, &self.class_traits)
}
/// Determines if this class provides a given trait on itself.
pub fn has_class_trait(&self, name: &QName) -> bool {
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`.
pub fn resolve_any_class_trait(&self, local_name: &str) -> Option<Namespace> {
for trait_entry in self.class_traits.iter() {
if local_name == trait_entry.name().local_name() {
return Some(trait_entry.name().namespace().clone());
}
}
None
}
/// 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,
name: &QName,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> {
do_trait_lookup(name, known_traits, &self.instance_traits)
}
/// Determines if this class provides a given trait on it's instances.
pub fn has_instance_trait(&self, name: &QName) -> bool {
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`.
pub fn resolve_any_instance_trait(&self, local_name: &str) -> Option<Namespace> {
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> {
self.instance_init
}
/// Get this class's class initializer.
pub fn class_init(&self) -> Method<'gc> {
self.class_init
}
} }

View File

@ -1,21 +1,20 @@
//! AVM2 executables. //! AVM2 executables.
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::Avm2ClassEntry; use crate::avm2::class::Class;
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::r#trait::Trait;
use crate::avm2::return_value::ReturnValue; use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData}; use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error}; use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use gc_arena::{Collect, CollectionContext, GcCell, MutationContext}; use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
use std::fmt; use std::fmt;
use std::rc::Rc; use std::rc::Rc;
use swf::avm2::types::{ use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody};
AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody, Trait as AbcTrait,
};
/// Represents a function defined in Ruffle's code. /// Represents a function defined in Ruffle's code.
/// ///
@ -125,6 +124,18 @@ impl<'gc> fmt::Debug for Method<'gc> {
} }
} }
impl<'gc> From<NativeFunction<'gc>> for Method<'gc> {
fn from(nf: NativeFunction<'gc>) -> Self {
Self::Native(nf)
}
}
impl<'gc> From<Avm2MethodEntry> for Method<'gc> {
fn from(a2me: Avm2MethodEntry) -> Self {
Self::Entry(a2me)
}
}
/// Represents an AVM2 function. /// Represents an AVM2 function.
#[derive(Collect, Clone, Debug)] #[derive(Collect, Clone, Debug)]
#[collect(no_drop)] #[collect(no_drop)]
@ -142,37 +153,64 @@ pub struct Avm2Function<'gc> {
pub reciever: Option<Object<'gc>>, pub reciever: Option<Object<'gc>>,
} }
impl<'gc> Avm2Function<'gc> {
pub fn from_method(
method: Avm2MethodEntry,
scope: Option<GcCell<'gc, Scope<'gc>>>,
reciever: Option<Object<'gc>>,
) -> Self {
Self {
method,
scope,
reciever,
}
}
}
/// Represents code that can be executed by some means. /// Represents code that can be executed by some means.
#[derive(Clone)] #[derive(Clone)]
pub enum Executable<'gc> { pub enum Executable<'gc> {
Native(NativeFunction<'gc>), /// Code defined in Ruffle's binary.
Action(Avm2Function<'gc>), ///
/// The second parameter stores the bound reciever for this function.
Native(NativeFunction<'gc>, Option<Object<'gc>>),
/// Code defined in a loaded ABC file.
Action {
/// The method code to execute from a given ABC file.
method: Avm2MethodEntry,
/// The scope stack to pull variables from.
scope: Option<GcCell<'gc, Scope<'gc>>>,
/// The reciever that this function is always called with.
///
/// If `None`, then the reciever provided by the caller is used. A
/// `Some` value indicates a bound executable.
reciever: Option<Object<'gc>>,
},
} }
unsafe impl<'gc> Collect for Executable<'gc> { unsafe impl<'gc> Collect for Executable<'gc> {
fn trace(&self, cc: CollectionContext) { fn trace(&self, cc: CollectionContext) {
match self { match self {
Self::Action(a2f) => a2f.trace(cc), Self::Action {
Self::Native(_nf) => {} method,
scope,
reciever,
} => {
method.trace(cc);
scope.trace(cc);
reciever.trace(cc);
}
Self::Native(_nf, reciever) => reciever.trace(cc),
} }
} }
} }
impl<'gc> Executable<'gc> { impl<'gc> Executable<'gc> {
/// Convert a method into an executable.
pub fn from_method(
method: Method<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
reciever: Option<Object<'gc>>,
) -> Self {
match method {
Method::Native(nf) => Self::Native(nf, reciever),
Method::Entry(a2me) => Self::Action {
method: a2me,
scope,
reciever,
},
}
}
/// Execute a method. /// Execute a method.
/// ///
/// The function will either be called directly if it is a Rust builtin, or /// The function will either be called directly if it is a Rust builtin, or
@ -189,12 +227,25 @@ impl<'gc> Executable<'gc> {
base_proto: Option<Object<'gc>>, base_proto: Option<Object<'gc>>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
match self { match self {
Executable::Native(nf) => nf(avm, context, unbound_reciever, arguments), Executable::Native(nf, reciever) => {
Executable::Action(a2f) => { nf(avm, context, reciever.or(unbound_reciever), arguments)
let reciever = a2f.reciever.or(unbound_reciever); }
Executable::Action {
method,
scope,
reciever,
} => {
let reciever = reciever.or(unbound_reciever);
let activation = GcCell::allocate( let activation = GcCell::allocate(
context.gc_context, context.gc_context,
Activation::from_action(context, &a2f, reciever, arguments, base_proto)?, Activation::from_action(
context,
method.clone(),
scope.clone(),
reciever,
arguments,
base_proto,
)?,
); );
avm.insert_stack_frame(activation); avm.insert_stack_frame(activation);
@ -207,27 +258,25 @@ impl<'gc> Executable<'gc> {
impl<'gc> fmt::Debug for Executable<'gc> { impl<'gc> fmt::Debug for Executable<'gc> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(), Self::Action {
Self::Native(nf) => fmt method,
scope,
reciever,
} => fmt
.debug_struct("Executable::Action")
.field("method", method)
.field("scope", scope)
.field("reciever", reciever)
.finish(),
Self::Native(nf, reciever) => fmt
.debug_tuple("Executable::Native") .debug_tuple("Executable::Native")
.field(&format!("{:p}", nf)) .field(&format!("{:p}", nf))
.field(reciever)
.finish(), .finish(),
} }
} }
} }
impl<'gc> From<NativeFunction<'gc>> for Executable<'gc> {
fn from(nf: NativeFunction<'gc>) -> Self {
Self::Native(nf)
}
}
impl<'gc> From<Avm2Function<'gc>> for Executable<'gc> {
fn from(a2f: Avm2Function<'gc>) -> Self {
Self::Action(a2f)
}
}
/// An Object which can be called to execute it's function code. /// An Object which can be called to execute it's function code.
#[derive(Collect, Debug, Clone, Copy)] #[derive(Collect, Debug, Clone, Copy)]
#[collect(no_drop)] #[collect(no_drop)]
@ -244,15 +293,15 @@ pub struct FunctionObjectData<'gc> {
} }
impl<'gc> FunctionObject<'gc> { impl<'gc> FunctionObject<'gc> {
/// Construct a class from an ABC class/instance pair. /// Construct a class.
/// ///
/// This function returns both the class itself, and the static class /// This function returns both the class itself, and the static class
/// initializer method that you should call before interacting with the /// initializer method that you should call before interacting with the
/// class. The latter should be called using the former as a reciever. /// class. The latter should be called using the former as a reciever.
pub fn from_abc_class( pub fn from_class(
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry, class: Gc<'gc, Class<'gc>>,
mut base_class: Object<'gc>, mut base_class: Object<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<(Object<'gc>, Object<'gc>), Error> { ) -> Result<(Object<'gc>, Object<'gc>), Error> {
@ -265,40 +314,20 @@ impl<'gc> FunctionObject<'gc> {
)? )?
.as_object() .as_object()
.map_err(|_| { .map_err(|_| {
let super_name = QName::from_abc_multiname( format!(
&class.abc(), "Could not resolve superclass prototype {:?}",
class.instance().super_name.clone(), class
); .super_class_name()
.map(|p| p.local_name())
if let Ok(super_name) = super_name { .unwrap_or(Some("Object"))
format!( )
"Could not resolve superclass prototype {:?}", .into()
super_name.local_name()
)
.into()
} else {
format!(
"Could not resolve superclass prototype, and got this error when getting it's name: {:?}",
super_name.unwrap_err()
)
.into()
}
}); });
let mut class_proto = super_proto?.derive(avm, context, class.clone(), scope)?; let mut class_proto = super_proto?.derive(avm, context, class, scope)?;
let fn_proto = avm.prototypes().function; let fn_proto = avm.prototypes().function;
let class_constr_proto = avm.prototypes().class; let class_constr_proto = avm.prototypes().class;
let initializer_index = class.instance().init_method.clone(); let initializer = class.instance_init();
let initializer: Result<Avm2MethodEntry, Error> =
Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else(
|| {
format!(
"Instance initializer method index {} does not exist",
initializer_index.0
)
.into()
},
);
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
context.gc_context, context.gc_context,
@ -307,7 +336,7 @@ impl<'gc> FunctionObject<'gc> {
Some(fn_proto), Some(fn_proto),
ScriptObjectClass::ClassConstructor(class.clone(), scope), ScriptObjectClass::ClassConstructor(class.clone(), scope),
), ),
exec: Some(Avm2Function::from_method(initializer?, scope, None).into()), exec: Some(Executable::from_method(initializer, scope, None).into()),
}, },
)) ))
.into(); .into();
@ -323,19 +352,10 @@ impl<'gc> FunctionObject<'gc> {
constr.into(), constr.into(),
)?; )?;
let class_initializer_index = class.class().init_method.clone(); let class_initializer = class.class_init();
let class_initializer: Result<Avm2MethodEntry, Error> = let class_constr = FunctionObject::from_method(
Avm2MethodEntry::from_method_index(class.abc(), class_initializer_index.clone())
.ok_or_else(|| {
format!(
"Class initializer method index {} does not exist",
class_initializer_index.0
)
.into()
});
let class_constr = FunctionObject::from_abc_method(
context.gc_context, context.gc_context,
class_initializer?, class_initializer,
scope, scope,
class_constr_proto, class_constr_proto,
None, None,
@ -348,14 +368,14 @@ impl<'gc> FunctionObject<'gc> {
/// ///
/// The given `reciever`, if supplied, will override any user-specified /// The given `reciever`, if supplied, will override any user-specified
/// `this` parameter. /// `this` parameter.
pub fn from_abc_method( pub fn from_method(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
method: Avm2MethodEntry, method: Method<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
reciever: Option<Object<'gc>>, reciever: Option<Object<'gc>>,
) -> Object<'gc> { ) -> Object<'gc> {
let exec = Some(Avm2Function::from_method(method, scope, reciever).into()); let exec = Some(Executable::from_method(method, scope, reciever));
FunctionObject(GcCell::allocate( FunctionObject(GcCell::allocate(
mc, mc,
@ -377,7 +397,7 @@ impl<'gc> FunctionObject<'gc> {
mc, mc,
FunctionObjectData { FunctionObjectData {
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
exec: Some(nf.into()), exec: Some(Executable::from_method(nf.into(), None, None)),
}, },
)) ))
.into() .into()
@ -394,7 +414,7 @@ impl<'gc> FunctionObject<'gc> {
mc, mc,
FunctionObjectData { FunctionObjectData {
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass), base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
exec: Some(constr.into()), exec: Some(Executable::from_method(constr.into(), None, None)),
}, },
)) ))
.into(); .into();
@ -500,14 +520,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0.read().base.get_method(id) self.0.read().base.get_method(id)
} }
fn get_trait(self, name: &QName) -> Result<Vec<AbcTrait>, Error> { fn get_trait(self, name: &QName) -> Result<Vec<Gc<'gc, Trait<'gc>>>, Error> {
self.0.read().base.get_trait(name) self.0.read().base.get_trait(name)
} }
fn get_provided_trait( fn get_provided_trait(
&self, &self,
name: &QName, name: &QName,
known_traits: &mut Vec<AbcTrait>, known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.0.read().base.get_provided_trait(name, known_traits) self.0.read().base.get_provided_trait(name, known_traits)
} }
@ -516,10 +536,6 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0.read().base.get_scope() self.0.read().base.get_scope()
} }
fn get_abc(self) -> Option<Rc<AbcFile>> {
self.0.read().base.get_abc()
}
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> { fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
self.0.read().base.resolve_any(local_name) self.0.read().base.resolve_any(local_name)
} }
@ -620,7 +636,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
&self, &self,
_avm: &mut Avm2<'gc>, _avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry, class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::FunctionObject(*self); let this: Object<'gc> = Object::FunctionObject(*self);
@ -638,8 +654,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
fn to_string(&self) -> Result<Value<'gc>, Error> { fn to_string(&self) -> Result<Value<'gc>, Error> {
if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() { if let ScriptObjectClass::ClassConstructor(class, ..) = self.0.read().base.class() {
let name = QName::from_abc_multiname(&class.abc(), class.instance().name.clone())?; Ok(format!("[class {}]", class.name().local_name()).into())
Ok(format!("[class {}]", name.local_name()).into())
} else { } else {
Ok("function Function() {}".into()) Ok("function Function() {}".into())
} }

View File

@ -1,18 +1,17 @@
//! AVM2 objects. //! AVM2 objects.
use crate::avm2::class::Avm2ClassEntry; use crate::avm2::class::Class;
use crate::avm2::function::{Avm2MethodEntry, Executable, FunctionObject}; use crate::avm2::function::{Executable, FunctionObject};
use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::r#trait::{Trait, TraitKind};
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::script_object::ScriptObject; use crate::avm2::script_object::ScriptObject;
use crate::avm2::value::{abc_default_value, Value}; use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error}; use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_macros::enum_trait_object; use ruffle_macros::enum_trait_object;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind};
/// Represents an object that can be directly interacted with by the AVM2 /// Represents an object that can be directly interacted with by the AVM2
/// runtime. /// runtime.
@ -45,7 +44,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if !self.has_instantiated_property(name) { if !self.has_instantiated_property(name) {
for abc_trait in self.get_trait(name)? { for abc_trait in self.get_trait(name)? {
self.install_trait(avm, context, &abc_trait, reciever)?; self.install_trait(avm, context, abc_trait, reciever)?;
} }
} }
@ -99,7 +98,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) -> Result<(), Error> { ) -> Result<(), Error> {
if !self.has_instantiated_property(name) { if !self.has_instantiated_property(name) {
for abc_trait in self.get_trait(name)? { for abc_trait in self.get_trait(name)? {
self.install_trait(avm, context, &abc_trait, reciever)?; self.install_trait(avm, context, abc_trait, reciever)?;
} }
} }
@ -143,7 +142,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) -> Result<(), Error> { ) -> Result<(), Error> {
if !self.has_instantiated_property(name) { if !self.has_instantiated_property(name) {
for abc_trait in self.get_trait(name)? { for abc_trait in self.get_trait(name)? {
self.install_trait(avm, context, &abc_trait, reciever)?; self.install_trait(avm, context, abc_trait, reciever)?;
} }
} }
@ -193,7 +192,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// This function returns `None` if no such trait exists, or the object /// This function returns `None` if no such trait exists, or the object
/// does not have traits. It returns `Err` if *any* trait in the object is /// does not have traits. It returns `Err` if *any* trait in the object is
/// malformed in some way. /// malformed in some way.
fn get_trait(self, name: &QName) -> Result<Vec<AbcTrait>, Error>; fn get_trait(self, name: &QName) -> Result<Vec<Gc<'gc, Trait<'gc>>>, Error>;
/// Populate a list of traits that this object provides. /// Populate a list of traits that this object provides.
/// ///
@ -204,7 +203,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
fn get_provided_trait( fn get_provided_trait(
&self, &self,
name: &QName, name: &QName,
known_traits: &mut Vec<AbcTrait>, known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error>; ) -> Result<(), Error>;
/// Retrieves the scope chain of the object at time of it's creation. /// Retrieves the scope chain of the object at time of it's creation.
@ -215,14 +214,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// this scope chain. /// this scope chain.
fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>>; fn get_scope(self) -> Option<GcCell<'gc, Scope<'gc>>>;
/// Retrieves the ABC file that this object, or it's class, was defined in.
///
/// Objects that were not defined in an ABC file or created from a class
/// defined in an ABC file will return `None`. This can happen for things
/// such as object or array literals. If this object does not have an ABC
/// file, then it must also not have traits.
fn get_abc(self) -> Option<Rc<AbcFile>>;
/// Resolve a multiname into a single QName, if any of the namespaces /// Resolve a multiname into a single QName, if any of the namespaces
/// match. /// match.
fn resolve_multiname(self, multiname: &Multiname) -> Result<Option<QName>, Error> { fn resolve_multiname(self, multiname: &Multiname) -> Result<Option<QName>, Error> {
@ -415,16 +406,10 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&mut self, &mut self,
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
trait_entry: &AbcTrait, trait_entry: Gc<'gc, Trait<'gc>>,
reciever: Object<'gc>, reciever: Object<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let scope = self.get_scope(); self.install_foreign_trait(avm, context, trait_entry, self.get_scope(), reciever)
let abc: Result<Rc<AbcFile>, Error> = self.get_abc().ok_or_else(|| {
"Object with traits must have an ABC file!"
.to_string()
.into()
});
self.install_foreign_trait(avm, context, abc?, trait_entry, scope, reciever)
} }
/// Install a trait from anywyere. /// Install a trait from anywyere.
@ -432,73 +417,78 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&mut self, &mut self,
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
abc: Rc<AbcFile>, trait_entry: Gc<'gc, Trait<'gc>>,
trait_entry: &AbcTrait,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
reciever: Object<'gc>, reciever: Object<'gc>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let fn_proto = avm.prototypes().function; let fn_proto = avm.prototypes().function;
let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?; let trait_name = trait_entry.name().clone();
avm_debug!( avm_debug!(
"Installing trait {:?} of kind {:?}", "Installing trait {:?} of kind {:?}",
trait_name, trait_name,
trait_entry.kind trait_entry.kind
); );
match &trait_entry.kind { match trait_entry.kind() {
AbcTraitKind::Slot { slot_id, value, .. } => { TraitKind::Slot {
let value = if let Some(value) = value { slot_id,
abc_default_value(&abc, value)? default_value,
} else { ..
Value::Undefined } => {
}; self.install_slot(
self.install_slot(context.gc_context, trait_name, *slot_id, value); context.gc_context,
trait_name,
*slot_id,
default_value.unwrap_or(Value::Undefined),
);
} }
AbcTraitKind::Method { TraitKind::Method {
disp_id, method, .. disp_id, method, ..
} => { } => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); let function = FunctionObject::from_method(
let function = FunctionObject::from_abc_method(
context.gc_context, context.gc_context,
method, method.clone(),
scope, scope,
fn_proto, fn_proto,
Some(reciever), Some(reciever),
); );
self.install_method(context.gc_context, trait_name, *disp_id, function); self.install_method(context.gc_context, trait_name, *disp_id, function);
} }
AbcTraitKind::Getter { TraitKind::Getter {
disp_id, method, .. disp_id, method, ..
} => { } => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); let function = FunctionObject::from_method(
let function = FunctionObject::from_abc_method(
context.gc_context, context.gc_context,
method, method.clone(),
scope, scope,
fn_proto, fn_proto,
Some(reciever), Some(reciever),
); );
self.install_getter(context.gc_context, trait_name, *disp_id, function)?; self.install_getter(context.gc_context, trait_name, *disp_id, function)?;
} }
AbcTraitKind::Setter { TraitKind::Setter {
disp_id, method, .. disp_id, method, ..
} => { } => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap(); let function = FunctionObject::from_method(
let function = FunctionObject::from_abc_method(
context.gc_context, context.gc_context,
method, method.clone(),
scope, scope,
fn_proto, fn_proto,
Some(reciever), Some(reciever),
); );
self.install_setter(context.gc_context, trait_name, *disp_id, function)?; self.install_setter(context.gc_context, trait_name, *disp_id, function)?;
} }
AbcTraitKind::Class { slot_id, class } => { TraitKind::Class { slot_id, class } => {
let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap(); //TODO: what happens if this happens on a class defined as a
let super_name = QName::from_abc_multiname( //class trait, without a superclass? How do we get `Object`
&type_entry.abc(), //then?
type_entry.instance().super_name.clone(), let super_name = if let Some(sc_name) = class.super_class_name() {
)?; self.resolve_multiname(sc_name)?
.unwrap_or(QName::dynamic_name("Object"))
} else {
QName::dynamic_name("Object")
};
let super_class: Result<Object<'gc>, Error> = self let super_class: Result<Object<'gc>, Error> = self
.get_property(reciever, &super_name, avm, context)? .get_property(reciever, &super_name, avm, context)?
.as_object() .as_object()
@ -506,47 +496,46 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
format!("Could not resolve superclass {:?}", super_name.local_name()).into() format!("Could not resolve superclass {:?}", super_name.local_name()).into()
}); });
let (class, _cinit) = FunctionObject::from_abc_class( let (class_object, _cinit) =
avm, FunctionObject::from_class(avm, context, *class, super_class?, scope)?;
context, self.install_const(
type_entry.clone(), context.gc_context,
super_class?, class.name().clone(),
scope, *slot_id,
)?; class_object.into(),
let class_name = QName::from_abc_multiname( );
&type_entry.abc(),
type_entry.instance().name.clone(),
)?;
self.install_const(context.gc_context, class_name, *slot_id, class.into());
} }
AbcTraitKind::Function { TraitKind::Function {
slot_id, function, .. slot_id, function, ..
} => { } => {
let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap(); let mut fobject = FunctionObject::from_method(
let mut function = FunctionObject::from_abc_method(
context.gc_context, context.gc_context,
method, function.clone(),
scope, scope,
fn_proto, fn_proto,
None, None,
); );
let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object); let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object);
function.install_slot( fobject.install_slot(
context.gc_context, context.gc_context,
QName::new(Namespace::public_namespace(), "prototype"), QName::new(Namespace::public_namespace(), "prototype"),
0, 0,
es3_proto.into(), es3_proto.into(),
); );
self.install_const(context.gc_context, trait_name, *slot_id, function.into()); self.install_const(context.gc_context, trait_name, *slot_id, fobject.into());
} }
AbcTraitKind::Const { slot_id, value, .. } => { TraitKind::Const {
let value = if let Some(value) = value { slot_id,
abc_default_value(&abc, value)? default_value,
} else { ..
Value::Undefined } => {
}; self.install_const(
self.install_const(context.gc_context, trait_name, *slot_id, value); context.gc_context,
trait_name,
*slot_id,
default_value.unwrap_or(Value::Undefined),
);
} }
} }
@ -602,7 +591,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&self, &self,
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry, class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>; ) -> Result<Object<'gc>, Error>;

View File

@ -1,21 +1,20 @@
//! Default AVM2 object impl //! Default AVM2 object impl
use crate::avm2::class::Avm2ClassEntry; use crate::avm2::class::Class;
use crate::avm2::function::Executable; use crate::avm2::function::Executable;
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, ObjectPtr, TObject}; use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::property::Property; use crate::avm2::property::Property;
use crate::avm2::r#trait::Trait;
use crate::avm2::return_value::ReturnValue; use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope; use crate::avm2::scope::Scope;
use crate::avm2::slot::Slot; use crate::avm2::slot::Slot;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error}; use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext; use crate::context::UpdateContext;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, Gc, GcCell, MutationContext};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind};
/// Default implementation of `avm2::Object`. /// Default implementation of `avm2::Object`.
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -34,10 +33,10 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
#[collect(no_drop)] #[collect(no_drop)]
pub enum ScriptObjectClass<'gc> { pub enum ScriptObjectClass<'gc> {
/// Instantiate instance traits, for prototypes. /// Instantiate instance traits, for prototypes.
InstancePrototype(Avm2ClassEntry, Option<GcCell<'gc, Scope<'gc>>>), InstancePrototype(Gc<'gc, Class<'gc>>, Option<GcCell<'gc, Scope<'gc>>>),
/// Instantiate class traits, for class constructors. /// Instantiate class traits, for class constructors.
ClassConstructor(Avm2ClassEntry, Option<GcCell<'gc, Scope<'gc>>>), ClassConstructor(Gc<'gc, Class<'gc>>, Option<GcCell<'gc, Scope<'gc>>>),
/// Do not instantiate any class or instance traits. /// Do not instantiate any class or instance traits.
NoClass, NoClass,
@ -153,14 +152,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().get_method(id) self.0.read().get_method(id)
} }
fn get_trait(self, name: &QName) -> Result<Vec<AbcTrait>, Error> { fn get_trait(self, name: &QName) -> Result<Vec<Gc<'gc, Trait<'gc>>>, Error> {
self.0.read().get_trait(name) self.0.read().get_trait(name)
} }
fn get_provided_trait( fn get_provided_trait(
&self, &self,
name: &QName, name: &QName,
known_traits: &mut Vec<AbcTrait>, known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.0.read().get_provided_trait(name, known_traits) self.0.read().get_provided_trait(name, known_traits)
} }
@ -169,10 +168,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().get_scope() self.0.read().get_scope()
} }
fn get_abc(self) -> Option<Rc<AbcFile>> {
self.0.read().get_abc()
}
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> { fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
self.0.read().resolve_any(local_name) self.0.read().resolve_any(local_name)
} }
@ -246,7 +241,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
&self, &self,
_avm: &mut Avm2<'gc>, _avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry, class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ScriptObject(*self); let this: Object<'gc> = Object::ScriptObject(*self);
@ -352,7 +347,7 @@ impl<'gc> ScriptObject<'gc> {
pub fn prototype( pub fn prototype(
mc: MutationContext<'gc, '_>, mc: MutationContext<'gc, '_>,
proto: Object<'gc>, proto: Object<'gc>,
class: Avm2ClassEntry, class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> { ) -> Object<'gc> {
let script_class = ScriptObjectClass::InstancePrototype(class, scope); let script_class = ScriptObjectClass::InstancePrototype(class, scope);
@ -365,51 +360,6 @@ impl<'gc> ScriptObject<'gc> {
} }
} }
/// Given a list of traits from an ABC file, find the one that matches this
/// name.
///
/// 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.
///
/// TODO: This is an O(n^2) algorithm, it sucks.
fn do_trait_lookup(
name: &QName,
known_traits: &mut Vec<AbcTrait>,
abc: Rc<AbcFile>,
traits: &[AbcTrait],
) -> Result<(), Error> {
for trait_entry in traits.iter() {
let trait_name = QName::from_abc_multiname(&abc, trait_entry.name.clone())?;
if name == &trait_name {
for known_trait in known_traits.iter() {
match (&trait_entry.kind, &known_trait.kind) {
(AbcTraitKind::Getter { .. }, AbcTraitKind::Setter { .. }) => continue,
(AbcTraitKind::Setter { .. }, AbcTraitKind::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> ScriptObjectData<'gc> { impl<'gc> ScriptObjectData<'gc> {
pub fn base_new(proto: Option<Object<'gc>>, trait_source: ScriptObjectClass<'gc>) -> Self { pub fn base_new(proto: Option<Object<'gc>>, trait_source: ScriptObjectClass<'gc>) -> Self {
ScriptObjectData { ScriptObjectData {
@ -576,7 +526,7 @@ impl<'gc> ScriptObjectData<'gc> {
self.methods.get(id as usize).and_then(|v| *v) self.methods.get(id as usize).and_then(|v| *v)
} }
pub fn get_trait(&self, name: &QName) -> Result<Vec<AbcTrait>, Error> { pub fn get_trait(&self, name: &QName) -> Result<Vec<Gc<'gc, Trait<'gc>>>, Error> {
match &self.class { match &self.class {
//Class constructors have local traits only. //Class constructors have local traits only.
ScriptObjectClass::ClassConstructor(..) => { ScriptObjectClass::ClassConstructor(..) => {
@ -614,14 +564,14 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_provided_trait( pub fn get_provided_trait(
&self, &self,
name: &QName, name: &QName,
known_traits: &mut Vec<AbcTrait>, known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
match &self.class { match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => { ScriptObjectClass::ClassConstructor(class, ..) => {
do_trait_lookup(name, known_traits, class.abc(), &class.class().traits) class.lookup_class_traits(name, known_traits)
} }
ScriptObjectClass::InstancePrototype(class, ..) => { ScriptObjectClass::InstancePrototype(class, ..) => {
do_trait_lookup(name, known_traits, class.abc(), &class.instance().traits) class.lookup_instance_traits(name, known_traits)
} }
ScriptObjectClass::NoClass => Ok(()), ScriptObjectClass::NoClass => Ok(()),
} }
@ -656,30 +606,10 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn provides_trait(&self, name: &QName) -> Result<bool, Error> { pub fn provides_trait(&self, name: &QName) -> Result<bool, Error> {
match &self.class { match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => { ScriptObjectClass::ClassConstructor(class, ..) => Ok(class.has_class_trait(name)),
for trait_entry in class.class().traits.iter() { ScriptObjectClass::InstancePrototype(class, ..) => Ok(class.has_instance_trait(name)),
let trait_name = ScriptObjectClass::NoClass => Ok(false),
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?; }
if name == &trait_name {
return Ok(true);
}
}
}
ScriptObjectClass::InstancePrototype(class, ..) => {
for trait_entry in class.instance().traits.iter() {
let trait_name =
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
if name == &trait_name {
return Ok(true);
}
}
}
ScriptObjectClass::NoClass => {}
};
Ok(false)
} }
pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> { pub fn get_scope(&self) -> Option<GcCell<'gc, Scope<'gc>>> {
@ -690,14 +620,6 @@ impl<'gc> ScriptObjectData<'gc> {
} }
} }
pub fn get_abc(&self) -> Option<Rc<AbcFile>> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => Some(class.abc()),
ScriptObjectClass::InstancePrototype(class, ..) => Some(class.abc()),
ScriptObjectClass::NoClass => self.proto().and_then(|proto| proto.get_abc()),
}
}
pub fn resolve_any(&self, local_name: &str) -> Result<Option<Namespace>, Error> { pub fn resolve_any(&self, local_name: &str) -> Result<Option<Namespace>, Error> {
for (key, _value) in self.values.iter() { for (key, _value) in self.values.iter() {
if key.local_name() == local_name { if key.local_name() == local_name {
@ -722,29 +644,13 @@ impl<'gc> ScriptObjectData<'gc> {
match &self.class { match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => { ScriptObjectClass::ClassConstructor(class, ..) => {
for trait_entry in class.class().traits.iter() { Ok(class.resolve_any_class_trait(local_name))
let trait_name =
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
if local_name == trait_name.local_name() {
return Ok(Some(trait_name.namespace().clone()));
}
}
} }
ScriptObjectClass::InstancePrototype(class, ..) => { ScriptObjectClass::InstancePrototype(class, ..) => {
for trait_entry in class.instance().traits.iter() { Ok(class.resolve_any_instance_trait(local_name))
let trait_name =
QName::from_abc_multiname(&class.abc(), trait_entry.name.clone())?;
if local_name == trait_name.local_name() {
return Ok(Some(trait_name.namespace().clone()));
}
}
} }
ScriptObjectClass::NoClass => {} ScriptObjectClass::NoClass => Ok(None),
}; }
Ok(None)
} }
pub fn has_own_property(&self, name: &QName) -> Result<bool, Error> { pub fn has_own_property(&self, name: &QName) -> Result<bool, Error> {

View File

@ -76,3 +76,21 @@ pub enum TraitKind<'gc> {
default_value: Option<Value<'gc>>, default_value: Option<Value<'gc>>,
}, },
} }
impl<'gc> Trait<'gc> {
pub fn name(&self) -> &QName {
&self.name
}
pub fn kind(&self) -> &TraitKind<'gc> {
&self.kind
}
pub fn is_final(&self) -> bool {
self.is_final
}
pub fn is_override(&self) -> bool {
self.is_override
}
}