Impl `newfunction` and `newclass`.

Notably, this also removes `new_closure_scope` as it is not needed. AVM1 does not capture `with` scopes in closures, but AVM2 (as well as modern ECMAScript) does.
This commit is contained in:
David Wendt 2020-02-22 17:54:38 -05:00
parent 1fe73b3329
commit 074ba94c17
4 changed files with 99 additions and 79 deletions

View File

@ -1,6 +1,7 @@
//! ActionScript Virtual Machine 2 (AS3) support //! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::activation::{Activation, Avm2ScriptEntry}; use crate::avm2::activation::{Activation, Avm2ScriptEntry};
use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject};
use crate::avm2::globals::SystemPrototypes; use crate::avm2::globals::SystemPrototypes;
use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::object::{Object, TObject}; use crate::avm2::object::{Object, TObject};
@ -14,8 +15,8 @@ use std::io::Cursor;
use std::rc::Rc; use std::rc::Rc;
use swf::avm2::read::Reader; use swf::avm2::read::Reader;
use swf::avm2::types::{ use swf::avm2::types::{
AbcFile, Index, MethodBody, Multiname as AbcMultiname, Namespace as AbcNamespace, Op, AbcFile, Class as AbcClass, Index, Method as AbcMethod, MethodBody, Multiname as AbcMultiname,
Script as AbcScript, Namespace as AbcNamespace, Op, Script as AbcScript,
}; };
use swf::read::SwfRead; use swf::read::SwfRead;
@ -370,6 +371,18 @@ impl<'gc> Avm2<'gc> {
Multiname::from_abc_multiname_static(&self.current_abc().unwrap(), index) Multiname::from_abc_multiname_static(&self.current_abc().unwrap(), index)
} }
/// Retrieve a method entry from the current ABC file's method table.
fn table_method(&mut self, index: Index<AbcMethod>) -> Result<Avm2MethodEntry, Error> {
Avm2MethodEntry::from_method_index(self.current_abc().unwrap(), index.clone())
.ok_or_else(|| format!("Method index {} does not exist", index.0).into())
}
/// Retrieve a class entry from the current ABC file's method table.
fn table_class(&mut self, index: Index<AbcClass>) -> Result<Avm2ClassEntry, Error> {
Avm2ClassEntry::from_class_index(self.current_abc().unwrap(), index.clone())
.ok_or_else(|| format!("Class index {} does not exist", index.0).into())
}
/// Run a single action from a given action reader. /// Run a single action from a given action reader.
pub fn do_next_opcode( pub fn do_next_opcode(
&mut self, &mut self,
@ -417,6 +430,8 @@ impl<'gc> Avm2<'gc> {
Op::ConstructProp { index, num_args } => { Op::ConstructProp { index, num_args } => {
self.op_construct_prop(context, index, num_args) self.op_construct_prop(context, index, num_args)
} }
Op::NewFunction { index } => self.op_new_function(context, index),
Op::NewClass { index } => self.op_new_class(context, index),
_ => self.unknown_op(op), _ => self.unknown_op(op),
}; };
@ -835,4 +850,47 @@ impl<'gc> Avm2<'gc> {
Ok(()) Ok(())
} }
fn op_new_function(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMethod>,
) -> Result<(), Error> {
let method_entry = self.table_method(index)?;
let scope = self.current_stack_frame().unwrap().read().scope();
let new_fn = FunctionObject::from_abc_method(
context.gc_context,
method_entry,
scope,
self.system_prototypes.function,
);
self.push(new_fn);
Ok(())
}
fn op_new_class(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcClass>,
) -> Result<(), Error> {
let base_class = self.pop().as_object()?;
let class_entry = self.table_class(index)?;
let scope = self.current_stack_frame().unwrap().read().scope();
let new_class = FunctionObject::from_abc_class(
self,
context,
class_entry,
base_class,
scope,
self.system_prototypes.function,
)?;
self.push(new_class);
Ok(())
}
} }

View File

@ -252,10 +252,44 @@ impl<'gc> FunctionObject<'gc> {
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
class: Avm2ClassEntry, class: Avm2ClassEntry,
proto: Object<'gc>, base_class: Object<'gc>,
scope: Option<GcCell<'gc, Scope<'gc>>>, scope: Option<GcCell<'gc, Scope<'gc>>>,
fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
let super_proto: Result<Object<'gc>, Error> = base_class
.get_property(
&QName::new(Namespace::public_namespace(), "prototype"),
avm,
context,
)?
.resolve(avm, context)?
.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()
}
});
let mut class_proto = super_proto?.construct(avm, context, &[])?;
for trait_entry in class.instance().traits.iter() {
class_proto.install_trait(avm, context, class.abc(), trait_entry, scope, fn_proto)?;
}
let initializer_index = class.class().init_method.clone(); let initializer_index = class.class().init_method.clone();
let initializer: Result<Avm2MethodEntry, Error> = let initializer: Result<Avm2MethodEntry, Error> =
Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else( Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else(
@ -267,6 +301,7 @@ impl<'gc> FunctionObject<'gc> {
.into() .into()
}, },
); );
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate( let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
context.gc_context, context.gc_context,
FunctionObjectData { FunctionObjectData {
@ -284,7 +319,7 @@ impl<'gc> FunctionObject<'gc> {
constr.install_method( constr.install_method(
context.gc_context, context.gc_context,
QName::new(Namespace::public_namespace(), "prototype"), QName::new(Namespace::public_namespace(), "prototype"),
proto, class_proto,
); );
Ok(constr) Ok(constr)

View File

@ -3,7 +3,7 @@
use crate::avm2::function::{ use crate::avm2::function::{
Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject, Avm2ClassEntry, Avm2Function, Avm2MethodEntry, Executable, FunctionObject,
}; };
use crate::avm2::names::{Multiname, Namespace, QName}; use crate::avm2::names::{Multiname, QName};
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::ScriptObject; use crate::avm2::script_object::ScriptObject;
@ -213,39 +213,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
.map_err(|_e| { .map_err(|_e| {
format!("Could not resolve superclass {:?}", super_name.local_name()).into() format!("Could not resolve superclass {:?}", super_name.local_name()).into()
}); });
let super_proto: Result<Object<'gc>, Error> = super_class?
.get_property(
&QName::new(Namespace::public_namespace(), "prototype"),
avm,
context,
)?
.resolve(avm, context)?
.as_object()
.map_err(|_e| {
format!(
"Could not resolve superclass prototype {:?}",
super_name.local_name()
)
.into()
});
let mut class_proto = super_proto?.construct(avm, context, &[])?;
for trait_entry in type_entry.instance().traits.iter() {
class_proto.install_trait(
avm,
context,
type_entry.abc(),
trait_entry,
scope,
fn_proto,
)?;
}
let class = FunctionObject::from_abc_class( let class = FunctionObject::from_abc_class(
avm, avm,
context, context,
type_entry.clone(), type_entry.clone(),
class_proto, super_class?,
scope, scope,
fn_proto, fn_proto,
)?; )?;

View File

@ -72,52 +72,6 @@ impl<'gc> Scope<'gc> {
self.parent self.parent
} }
/// Construct a closure scope to be used as the scope stack when invoking a
/// function.
///
/// This function filters With scopes from the scope chain. If all scopes
/// are filtered, this function returns None, representing an empty scope
/// stack.
pub fn new_closure_scope(
mut parent: GcCell<'gc, Self>,
mc: MutationContext<'gc, '_>,
) -> Option<GcCell<'gc, Self>> {
let mut bottom_scope = None;
let mut top_scope: Option<GcCell<'gc, Self>> = None;
loop {
if parent.read().class != ScopeClass::With {
let next_scope = GcCell::allocate(
mc,
Self {
parent: None,
class: parent.read().class,
values: parent.read().values,
},
);
if bottom_scope.is_none() {
bottom_scope = Some(next_scope);
}
if let Some(ref scope) = top_scope {
scope.write(mc).parent = Some(next_scope);
}
top_scope = Some(next_scope);
}
let grandparent = parent.read().parent;
if let Some(grandparent) = grandparent {
parent = grandparent;
} else {
break;
}
}
bottom_scope
}
/// Returns a reference to the current local scope object. /// Returns a reference to the current local scope object.
pub fn locals(&self) -> &Object<'gc> { pub fn locals(&self) -> &Object<'gc> {
&self.values &self.values