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:
parent
15a62d31cb
commit
70e9030072
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue