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:
parent
1fe73b3329
commit
074ba94c17
|
@ -1,6 +1,7 @@
|
|||
//! ActionScript Virtual Machine 2 (AS3) support
|
||||
|
||||
use crate::avm2::activation::{Activation, Avm2ScriptEntry};
|
||||
use crate::avm2::function::{Avm2ClassEntry, Avm2MethodEntry, FunctionObject};
|
||||
use crate::avm2::globals::SystemPrototypes;
|
||||
use crate::avm2::names::{Multiname, Namespace, QName};
|
||||
use crate::avm2::object::{Object, TObject};
|
||||
|
@ -14,8 +15,8 @@ use std::io::Cursor;
|
|||
use std::rc::Rc;
|
||||
use swf::avm2::read::Reader;
|
||||
use swf::avm2::types::{
|
||||
AbcFile, Index, MethodBody, Multiname as AbcMultiname, Namespace as AbcNamespace, Op,
|
||||
Script as AbcScript,
|
||||
AbcFile, Class as AbcClass, Index, Method as AbcMethod, MethodBody, Multiname as AbcMultiname,
|
||||
Namespace as AbcNamespace, Op, Script as AbcScript,
|
||||
};
|
||||
use swf::read::SwfRead;
|
||||
|
||||
|
@ -370,6 +371,18 @@ impl<'gc> Avm2<'gc> {
|
|||
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.
|
||||
pub fn do_next_opcode(
|
||||
&mut self,
|
||||
|
@ -417,6 +430,8 @@ impl<'gc> Avm2<'gc> {
|
|||
Op::ConstructProp { 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),
|
||||
};
|
||||
|
||||
|
@ -835,4 +850,47 @@ impl<'gc> Avm2<'gc> {
|
|||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -252,10 +252,44 @@ impl<'gc> FunctionObject<'gc> {
|
|||
avm: &mut Avm2<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
class: Avm2ClassEntry,
|
||||
proto: Object<'gc>,
|
||||
base_class: Object<'gc>,
|
||||
scope: Option<GcCell<'gc, Scope<'gc>>>,
|
||||
fn_proto: Object<'gc>,
|
||||
) -> 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: Result<Avm2MethodEntry, Error> =
|
||||
Avm2MethodEntry::from_method_index(class.abc(), initializer_index.clone()).ok_or_else(
|
||||
|
@ -267,6 +301,7 @@ impl<'gc> FunctionObject<'gc> {
|
|||
.into()
|
||||
},
|
||||
);
|
||||
|
||||
let mut constr: Object<'gc> = FunctionObject(GcCell::allocate(
|
||||
context.gc_context,
|
||||
FunctionObjectData {
|
||||
|
@ -284,7 +319,7 @@ impl<'gc> FunctionObject<'gc> {
|
|||
constr.install_method(
|
||||
context.gc_context,
|
||||
QName::new(Namespace::public_namespace(), "prototype"),
|
||||
proto,
|
||||
class_proto,
|
||||
);
|
||||
|
||||
Ok(constr)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use crate::avm2::function::{
|
||||
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::scope::Scope;
|
||||
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| {
|
||||
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(
|
||||
avm,
|
||||
context,
|
||||
type_entry.clone(),
|
||||
class_proto,
|
||||
super_class?,
|
||||
scope,
|
||||
fn_proto,
|
||||
)?;
|
||||
|
|
|
@ -72,52 +72,6 @@ impl<'gc> Scope<'gc> {
|
|||
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.
|
||||
pub fn locals(&self) -> &Object<'gc> {
|
||||
&self.values
|
||||
|
|
Loading…
Reference in New Issue