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
|
//! 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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue