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

View File

@ -1,9 +1,10 @@
//! AVM2 classes
use crate::avm2::function::Executable;
use crate::avm2::function::Method;
use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::r#trait::Trait;
use gc_arena::Collect;
use crate::avm2::r#trait::{Trait, TraitKind};
use crate::avm2::Error;
use gc_arena::{Collect, Gc};
use std::rc::Rc;
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.
///
/// 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.
///
/// These are accessed as normal instance properties; they should not be
/// present on prototypes, but instead should shadow any prototype
/// properties that would match.
instance_traits: Vec<Trait<'gc>>,
instance_traits: Vec<Gc<'gc, Trait<'gc>>>,
/// The class initializer for 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.
///
/// 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.
use crate::avm2::activation::Activation;
use crate::avm2::class::Avm2ClassEntry;
use crate::avm2::class::Class;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::r#trait::Trait;
use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope;
use crate::avm2::script_object::{ScriptObjectClass, ScriptObjectData};
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext;
use gc_arena::{Collect, CollectionContext, GcCell, MutationContext};
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
use std::fmt;
use std::rc::Rc;
use swf::avm2::types::{
AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody, Trait as AbcTrait,
};
use swf::avm2::types::{AbcFile, Index, Method as AbcMethod, MethodBody as AbcMethodBody};
/// 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.
#[derive(Collect, Clone, Debug)]
#[collect(no_drop)]
@ -142,37 +153,64 @@ pub struct Avm2Function<'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.
#[derive(Clone)]
pub enum Executable<'gc> {
Native(NativeFunction<'gc>),
Action(Avm2Function<'gc>),
/// Code defined in Ruffle's binary.
///
/// 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> {
fn trace(&self, cc: CollectionContext) {
match self {
Self::Action(a2f) => a2f.trace(cc),
Self::Native(_nf) => {}
Self::Action {
method,
scope,
reciever,
} => {
method.trace(cc);
scope.trace(cc);
reciever.trace(cc);
}
Self::Native(_nf, reciever) => reciever.trace(cc),
}
}
}
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.
///
/// 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>>,
) -> Result<ReturnValue<'gc>, Error> {
match self {
Executable::Native(nf) => nf(avm, context, unbound_reciever, arguments),
Executable::Action(a2f) => {
let reciever = a2f.reciever.or(unbound_reciever);
Executable::Native(nf, reciever) => {
nf(avm, context, reciever.or(unbound_reciever), arguments)
}
Executable::Action {
method,
scope,
reciever,
} => {
let reciever = reciever.or(unbound_reciever);
let activation = GcCell::allocate(
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);
@ -207,27 +258,25 @@ impl<'gc> Executable<'gc> {
impl<'gc> fmt::Debug for Executable<'gc> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Action(a2f) => fmt.debug_tuple("Executable::Action").field(a2f).finish(),
Self::Native(nf) => fmt
Self::Action {
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")
.field(&format!("{:p}", nf))
.field(reciever)
.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.
#[derive(Collect, Debug, Clone, Copy)]
#[collect(no_drop)]
@ -244,15 +293,15 @@ pub struct FunctionObjectData<'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
/// initializer method that you should call before interacting with the
/// 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>,
context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry,
class: Gc<'gc, Class<'gc>>,
mut base_class: Object<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<(Object<'gc>, Object<'gc>), Error> {
@ -265,40 +314,20 @@ impl<'gc> FunctionObject<'gc> {
)?
.as_object()
.map_err(|_| {
let super_name = QName::from_abc_multiname(
&class.abc(),
class.instance().super_name.clone(),
);
if let Ok(super_name) = super_name {
format!(
"Could not resolve superclass prototype {:?}",
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()
}
format!(
"Could not resolve superclass prototype {:?}",
class
.super_class_name()
.map(|p| p.local_name())
.unwrap_or(Some("Object"))
)
.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 class_constr_proto = avm.prototypes().class;
let initializer_index = class.instance().init_method.clone();
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 initializer = class.instance_init();
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
context.gc_context,
@ -307,7 +336,7 @@ impl<'gc> FunctionObject<'gc> {
Some(fn_proto),
ScriptObjectClass::ClassConstructor(class.clone(), scope),
),
exec: Some(Avm2Function::from_method(initializer?, scope, None).into()),
exec: Some(Executable::from_method(initializer, scope, None).into()),
},
))
.into();
@ -323,19 +352,10 @@ impl<'gc> FunctionObject<'gc> {
constr.into(),
)?;
let class_initializer_index = class.class().init_method.clone();
let class_initializer: Result<Avm2MethodEntry, Error> =
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(
let class_initializer = class.class_init();
let class_constr = FunctionObject::from_method(
context.gc_context,
class_initializer?,
class_initializer,
scope,
class_constr_proto,
None,
@ -348,14 +368,14 @@ impl<'gc> FunctionObject<'gc> {
///
/// The given `reciever`, if supplied, will override any user-specified
/// `this` parameter.
pub fn from_abc_method(
pub fn from_method(
mc: MutationContext<'gc, '_>,
method: Avm2MethodEntry,
method: Method<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
fn_proto: Object<'gc>,
reciever: Option<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(
mc,
@ -377,7 +397,7 @@ impl<'gc> FunctionObject<'gc> {
mc,
FunctionObjectData {
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
exec: Some(nf.into()),
exec: Some(Executable::from_method(nf.into(), None, None)),
},
))
.into()
@ -394,7 +414,7 @@ impl<'gc> FunctionObject<'gc> {
mc,
FunctionObjectData {
base: ScriptObjectData::base_new(Some(fn_proto), ScriptObjectClass::NoClass),
exec: Some(constr.into()),
exec: Some(Executable::from_method(constr.into(), None, None)),
},
))
.into();
@ -500,14 +520,14 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
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)
}
fn get_provided_trait(
&self,
name: &QName,
known_traits: &mut Vec<AbcTrait>,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> {
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()
}
fn get_abc(self) -> Option<Rc<AbcFile>> {
self.0.read().base.get_abc()
}
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
self.0.read().base.resolve_any(local_name)
}
@ -620,7 +636,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
&self,
_avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry,
class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
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> {
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 {}]", name.local_name()).into())
Ok(format!("[class {}]", class.name().local_name()).into())
} else {
Ok("function Function() {}".into())
}

View File

@ -1,18 +1,17 @@
//! AVM2 objects.
use crate::avm2::class::Avm2ClassEntry;
use crate::avm2::function::{Avm2MethodEntry, Executable, FunctionObject};
use crate::avm2::class::Class;
use crate::avm2::function::{Executable, FunctionObject};
use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::r#trait::{Trait, TraitKind};
use crate::avm2::scope::Scope;
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::context::UpdateContext;
use gc_arena::{Collect, GcCell, MutationContext};
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use ruffle_macros::enum_trait_object;
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
/// runtime.
@ -45,7 +44,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
) -> Result<Value<'gc>, Error> {
if !self.has_instantiated_property(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> {
if !self.has_instantiated_property(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> {
if !self.has_instantiated_property(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
/// does not have traits. It returns `Err` if *any* trait in the object is
/// 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.
///
@ -204,7 +203,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
fn get_provided_trait(
&self,
name: &QName,
known_traits: &mut Vec<AbcTrait>,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error>;
/// 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.
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
/// match.
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,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
trait_entry: &AbcTrait,
trait_entry: Gc<'gc, Trait<'gc>>,
reciever: Object<'gc>,
) -> Result<(), Error> {
let scope = self.get_scope();
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)
self.install_foreign_trait(avm, context, trait_entry, self.get_scope(), reciever)
}
/// Install a trait from anywyere.
@ -432,73 +417,78 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&mut self,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
abc: Rc<AbcFile>,
trait_entry: &AbcTrait,
trait_entry: Gc<'gc, Trait<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
reciever: Object<'gc>,
) -> Result<(), Error> {
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!(
"Installing trait {:?} of kind {:?}",
trait_name,
trait_entry.kind
);
match &trait_entry.kind {
AbcTraitKind::Slot { slot_id, value, .. } => {
let value = if let Some(value) = value {
abc_default_value(&abc, value)?
} else {
Value::Undefined
};
self.install_slot(context.gc_context, trait_name, *slot_id, value);
match trait_entry.kind() {
TraitKind::Slot {
slot_id,
default_value,
..
} => {
self.install_slot(
context.gc_context,
trait_name,
*slot_id,
default_value.unwrap_or(Value::Undefined),
);
}
AbcTraitKind::Method {
TraitKind::Method {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let function = FunctionObject::from_abc_method(
let function = FunctionObject::from_method(
context.gc_context,
method,
method.clone(),
scope,
fn_proto,
Some(reciever),
);
self.install_method(context.gc_context, trait_name, *disp_id, function);
}
AbcTraitKind::Getter {
TraitKind::Getter {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let function = FunctionObject::from_abc_method(
let function = FunctionObject::from_method(
context.gc_context,
method,
method.clone(),
scope,
fn_proto,
Some(reciever),
);
self.install_getter(context.gc_context, trait_name, *disp_id, function)?;
}
AbcTraitKind::Setter {
TraitKind::Setter {
disp_id, method, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, method.clone()).unwrap();
let function = FunctionObject::from_abc_method(
let function = FunctionObject::from_method(
context.gc_context,
method,
method.clone(),
scope,
fn_proto,
Some(reciever),
);
self.install_setter(context.gc_context, trait_name, *disp_id, function)?;
}
AbcTraitKind::Class { slot_id, class } => {
let type_entry = Avm2ClassEntry::from_class_index(abc, class.clone()).unwrap();
let super_name = QName::from_abc_multiname(
&type_entry.abc(),
type_entry.instance().super_name.clone(),
)?;
TraitKind::Class { slot_id, class } => {
//TODO: what happens if this happens on a class defined as a
//class trait, without a superclass? How do we get `Object`
//then?
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
.get_property(reciever, &super_name, avm, context)?
.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()
});
let (class, _cinit) = FunctionObject::from_abc_class(
avm,
context,
type_entry.clone(),
super_class?,
scope,
)?;
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());
let (class_object, _cinit) =
FunctionObject::from_class(avm, context, *class, super_class?, scope)?;
self.install_const(
context.gc_context,
class.name().clone(),
*slot_id,
class_object.into(),
);
}
AbcTraitKind::Function {
TraitKind::Function {
slot_id, function, ..
} => {
let method = Avm2MethodEntry::from_method_index(abc, function.clone()).unwrap();
let mut function = FunctionObject::from_abc_method(
let mut fobject = FunctionObject::from_method(
context.gc_context,
method,
function.clone(),
scope,
fn_proto,
None,
);
let es3_proto = ScriptObject::object(context.gc_context, avm.prototypes().object);
function.install_slot(
fobject.install_slot(
context.gc_context,
QName::new(Namespace::public_namespace(), "prototype"),
0,
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, .. } => {
let value = if let Some(value) = value {
abc_default_value(&abc, value)?
} else {
Value::Undefined
};
self.install_const(context.gc_context, trait_name, *slot_id, value);
TraitKind::Const {
slot_id,
default_value,
..
} => {
self.install_const(
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,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry,
class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error>;

View File

@ -1,21 +1,20 @@
//! Default AVM2 object impl
use crate::avm2::class::Avm2ClassEntry;
use crate::avm2::class::Class;
use crate::avm2::function::Executable;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::property::Property;
use crate::avm2::r#trait::Trait;
use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope;
use crate::avm2::slot::Slot;
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext;
use gc_arena::{Collect, GcCell, MutationContext};
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use std::collections::HashMap;
use std::fmt::Debug;
use std::rc::Rc;
use swf::avm2::types::{AbcFile, Trait as AbcTrait, TraitKind as AbcTraitKind};
/// Default implementation of `avm2::Object`.
#[derive(Clone, Collect, Debug, Copy)]
@ -34,10 +33,10 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
#[collect(no_drop)]
pub enum ScriptObjectClass<'gc> {
/// 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.
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.
NoClass,
@ -153,14 +152,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
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)
}
fn get_provided_trait(
&self,
name: &QName,
known_traits: &mut Vec<AbcTrait>,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> {
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()
}
fn get_abc(self) -> Option<Rc<AbcFile>> {
self.0.read().get_abc()
}
fn resolve_any(self, local_name: &str) -> Result<Option<Namespace>, Error> {
self.0.read().resolve_any(local_name)
}
@ -246,7 +241,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
&self,
_avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry,
class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Result<Object<'gc>, Error> {
let this: Object<'gc> = Object::ScriptObject(*self);
@ -352,7 +347,7 @@ impl<'gc> ScriptObject<'gc> {
pub fn prototype(
mc: MutationContext<'gc, '_>,
proto: Object<'gc>,
class: Avm2ClassEntry,
class: Gc<'gc, Class<'gc>>,
scope: Option<GcCell<'gc, Scope<'gc>>>,
) -> Object<'gc> {
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> {
pub fn base_new(proto: Option<Object<'gc>>, trait_source: ScriptObjectClass<'gc>) -> Self {
ScriptObjectData {
@ -576,7 +526,7 @@ impl<'gc> ScriptObjectData<'gc> {
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 {
//Class constructors have local traits only.
ScriptObjectClass::ClassConstructor(..) => {
@ -614,14 +564,14 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_provided_trait(
&self,
name: &QName,
known_traits: &mut Vec<AbcTrait>,
known_traits: &mut Vec<Gc<'gc, Trait<'gc>>>,
) -> Result<(), Error> {
match &self.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, ..) => {
do_trait_lookup(name, known_traits, class.abc(), &class.instance().traits)
class.lookup_instance_traits(name, known_traits)
}
ScriptObjectClass::NoClass => Ok(()),
}
@ -656,30 +606,10 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn provides_trait(&self, name: &QName) -> Result<bool, Error> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
for trait_entry in class.class().traits.iter() {
let trait_name =
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)
ScriptObjectClass::ClassConstructor(class, ..) => Ok(class.has_class_trait(name)),
ScriptObjectClass::InstancePrototype(class, ..) => Ok(class.has_instance_trait(name)),
ScriptObjectClass::NoClass => Ok(false),
}
}
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> {
for (key, _value) in self.values.iter() {
if key.local_name() == local_name {
@ -722,29 +644,13 @@ impl<'gc> ScriptObjectData<'gc> {
match &self.class {
ScriptObjectClass::ClassConstructor(class, ..) => {
for trait_entry in class.class().traits.iter() {
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()));
}
}
Ok(class.resolve_any_class_trait(local_name))
}
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 local_name == trait_name.local_name() {
return Ok(Some(trait_name.namespace().clone()));
}
}
Ok(class.resolve_any_instance_trait(local_name))
}
ScriptObjectClass::NoClass => {}
};
Ok(None)
ScriptObjectClass::NoClass => Ok(None),
}
}
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>>,
},
}
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
}
}