avm1: Merge #147, AS2 OOP operations

ActionScript 2 OOP operations
This commit is contained in:
Mike Welsh 2019-12-15 14:44:31 -08:00 committed by GitHub
commit f55e8036bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 721 additions and 32 deletions

View File

@ -27,6 +27,7 @@ mod return_value;
mod scope;
pub mod script_object;
mod stage_object;
mod super_object;
mod value;
#[cfg(test)]
@ -51,8 +52,9 @@ pub struct Avm1<'gc> {
/// The Flash Player version we're emulating.
player_version: u8,
/// The currently installed constant pool.
constant_pool: Vec<String>,
/// The constant pool to use for new activations from code sources that
/// don't close over the constant pool they were defined with.
constant_pool: GcCell<'gc, Vec<String>>,
/// The global object.
globals: Object<'gc>,
@ -78,6 +80,7 @@ unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
self.globals.trace(cc);
self.constant_pool.trace(cc);
self.prototypes.trace(cc);
self.display_properties.trace(cc);
self.stack_frames.trace(cc);
@ -97,7 +100,7 @@ impl<'gc> Avm1<'gc> {
Self {
player_version,
constant_pool: vec![],
constant_pool: GcCell::allocate(gc_context, vec![]),
globals,
prototypes,
display_properties: stage_object::DisplayPropertyMap::new(gc_context),
@ -170,7 +173,14 @@ impl<'gc> Avm1<'gc> {
);
self.stack_frames.push(GcCell::allocate(
action_context.gc_context,
Activation::from_action(swf_version, code, child_scope, clip_obj, None),
Activation::from_action(
swf_version,
code,
child_scope,
self.constant_pool,
clip_obj,
None,
),
));
}
@ -197,7 +207,14 @@ impl<'gc> Avm1<'gc> {
self.push(Value::Undefined);
self.stack_frames.push(GcCell::allocate(
action_context.gc_context,
Activation::from_action(swf_version, code, child_scope, clip_obj, None),
Activation::from_action(
swf_version,
code,
child_scope,
self.constant_pool,
clip_obj,
None,
),
));
}
@ -370,6 +387,7 @@ impl<'gc> Avm1<'gc> {
Action::Call => self.action_call(context),
Action::CallFunction => self.action_call_function(context),
Action::CallMethod => self.action_call_method(context),
Action::CastOp => self.action_cast_op(context),
Action::CharToAscii => self.action_char_to_ascii(context),
Action::CloneSprite => self.action_clone_sprite(context),
Action::ConstantPool(constant_pool) => {
@ -392,6 +410,7 @@ impl<'gc> Avm1<'gc> {
Action::Enumerate2 => self.action_enumerate_2(context),
Action::Equals => self.action_equals(context),
Action::Equals2 => self.action_equals_2(context),
Action::Extends => self.action_extends(context),
Action::GetMember => self.action_get_member(context),
Action::GetProperty => self.action_get_property(context),
Action::GetTime => self.action_get_time(context),
@ -415,6 +434,7 @@ impl<'gc> Avm1<'gc> {
Action::Increment => self.action_increment(context),
Action::InitArray => self.action_init_array(context),
Action::InitObject => self.action_init_object(context),
Action::ImplementsOp => self.action_implements_op(context),
Action::InstanceOf => self.action_instance_of(context),
Action::Jump { offset } => self.action_jump(context, offset, reader),
Action::Less => self.action_less(context),
@ -808,12 +828,38 @@ impl<'gc> Avm1<'gc> {
Ok(())
}
fn action_cast_op(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let obj = self.pop()?.as_object()?;
let constr = self.pop()?.as_object()?;
let prototype = constr
.get("prototype", self, context)?
.resolve(self, context)?
.as_object()?;
if obj.is_instance_of(self, context, constr, prototype)? {
self.push(obj);
} else {
self.push(Value::Null);
}
Ok(())
}
fn action_constant_pool(
&mut self,
_context: &mut UpdateContext,
context: &mut UpdateContext<'_, 'gc, '_>,
constant_pool: &[&str],
) -> Result<(), Error> {
self.constant_pool = constant_pool.iter().map(|s| s.to_string()).collect();
self.constant_pool = GcCell::allocate(
context.gc_context,
constant_pool.iter().map(|s| s.to_string()).collect(),
);
self.current_stack_frame()
.unwrap()
.write(context.gc_context)
.set_constant_pool(self.constant_pool);
Ok(())
}
@ -842,7 +888,9 @@ impl<'gc> Avm1<'gc> {
self.current_stack_frame().unwrap().read().scope_cell(),
context.gc_context,
);
let func = Avm1Function::from_df1(swf_version, func_data, name, params, scope);
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
let func =
Avm1Function::from_df1(swf_version, func_data, name, params, scope, constant_pool);
let prototype =
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
let func_obj = ScriptObject::function(
@ -880,7 +928,9 @@ impl<'gc> Avm1<'gc> {
self.current_stack_frame().unwrap().read().scope_cell(),
context.gc_context,
);
let func = Avm1Function::from_df2(swf_version, func_data, action_func, scope);
let constant_pool = self.current_stack_frame().unwrap().read().constant_pool();
let func =
Avm1Function::from_df2(swf_version, func_data, action_func, scope, constant_pool);
let prototype =
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
let func_obj = ScriptObject::function(
@ -1034,6 +1084,27 @@ impl<'gc> Avm1<'gc> {
Ok(())
}
fn action_extends(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let superclass = self.pop()?.as_object()?;
let subclass = self.pop()?.as_object()?;
//TODO: What happens if we try to extend an object which has no `prototype`?
//e.g. `class Whatever extends Object.prototype` or `class Whatever extends 5`
let super_proto = superclass
.get("prototype", self, context)?
.resolve(self, context)
.and_then(|val| val.as_object())
.unwrap_or(self.prototypes.object);
let sub_prototype: Object<'gc> =
ScriptObject::object(context.gc_context, Some(super_proto)).into();
sub_prototype.set("constructor", superclass.into(), self, context)?;
subclass.set("prototype", sub_prototype.into(), self, context)?;
Ok(())
}
fn action_get_member(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let name_val = self.pop()?;
let name = name_val.coerce_to_string(self, context)?;
@ -1324,6 +1395,30 @@ impl<'gc> Avm1<'gc> {
Ok(())
}
fn action_implements_op(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let constr = self.pop()?.as_object()?;
let count = self.pop()?.as_i64()?; //TODO: Is this coercion actually performed by Flash?
let mut interfaces = vec![];
//TODO: If one of the interfaces is not an object, do we leave the
//whole stack dirty, or...?
for _ in 0..count {
interfaces.push(self.pop()?.as_object()?);
}
let mut prototype = constr
.get("prototype", self, context)?
.resolve(self, context)?
.as_object()?;
prototype.set_interfaces(context.gc_context, interfaces);
Ok(())
}
fn action_instance_of(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
@ -1331,23 +1426,13 @@ impl<'gc> Avm1<'gc> {
let constr = self.pop()?.as_object()?;
let obj = self.pop()?.as_object()?;
//TODO: Interface detection on SWF7
let prototype = constr
.get("prototype", self, context)?
.resolve(self, context)?
.as_object()?;
let mut proto = obj.proto();
let is_instance_of = obj.is_instance_of(self, context, constr, prototype)?;
while let Some(this_proto) = proto {
if Object::ptr_eq(this_proto, prototype) {
self.push(true);
return Ok(());
}
proto = this_proto.proto();
}
self.push(false);
self.push(is_instance_of);
Ok(())
}
@ -1583,13 +1668,25 @@ impl<'gc> Avm1<'gc> {
SwfValue::Str(v) => v.to_string().into(),
SwfValue::Register(v) => self.current_register(*v),
SwfValue::ConstantPool(i) => {
if let Some(value) = self.constant_pool.get(*i as usize) {
if let Some(value) = self
.current_stack_frame()
.unwrap()
.read()
.constant_pool()
.read()
.get(*i as usize)
{
value.to_string().into()
} else {
log::warn!(
"ActionPush: Constant pool index {} out of range (len = {})",
i,
self.constant_pool.len()
self.current_stack_frame()
.unwrap()
.read()
.constant_pool()
.read()
.len()
);
Value::Undefined
}

View File

@ -66,6 +66,9 @@ pub struct Activation<'gc> {
/// All defined local variables in this stack frame.
scope: GcCell<'gc, Scope<'gc>>,
/// The currently in use constant pool.
constant_pool: GcCell<'gc, Vec<String>>,
/// The immutable value of `this`.
this: Object<'gc>,
@ -114,6 +117,7 @@ impl<'gc> Activation<'gc> {
swf_version: u8,
code: SwfSlice,
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<String>>,
this: Object<'gc>,
arguments: Option<Object<'gc>>,
) -> Activation<'gc> {
@ -122,6 +126,7 @@ impl<'gc> Activation<'gc> {
data: code,
pc: 0,
scope,
constant_pool,
this,
arguments,
return_value: None,
@ -135,6 +140,7 @@ impl<'gc> Activation<'gc> {
swf_version: u8,
code: SwfSlice,
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<String>>,
this: Object<'gc>,
arguments: Option<Object<'gc>>,
) -> Activation<'gc> {
@ -143,6 +149,7 @@ impl<'gc> Activation<'gc> {
data: code,
pc: 0,
scope,
constant_pool,
this,
arguments,
return_value: None,
@ -166,6 +173,7 @@ impl<'gc> Activation<'gc> {
) -> Activation<'gc> {
let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals));
let child_scope = GcCell::allocate(mc, Scope::new_local_scope(global_scope, mc));
let empty_constant_pool = GcCell::allocate(mc, Vec::new());
Activation {
swf_version,
@ -176,6 +184,7 @@ impl<'gc> Activation<'gc> {
},
pc: 0,
scope: child_scope,
constant_pool: empty_constant_pool,
this: globals,
arguments: None,
return_value: None,
@ -192,6 +201,7 @@ impl<'gc> Activation<'gc> {
data: code,
pc: 0,
scope,
constant_pool: self.constant_pool,
this: self.this,
arguments: self.arguments,
return_value: None,
@ -342,6 +352,14 @@ impl<'gc> Activation<'gc> {
}
}
pub fn constant_pool(&self) -> GcCell<'gc, Vec<String>> {
self.constant_pool
}
pub fn set_constant_pool(&mut self, constant_pool: GcCell<'gc, Vec<String>>) {
self.constant_pool = constant_pool;
}
/// Attempts to lock the activation frame for execution.
///
/// If this frame is already executing, that is an error condition.

View File

@ -4,6 +4,7 @@ use crate::avm1::activation::Activation;
use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::scope::Scope;
use crate::avm1::super_object::SuperObject;
use crate::avm1::value::Value;
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext};
use crate::display_object::TDisplayObject;
@ -66,6 +67,9 @@ pub struct Avm1Function<'gc> {
/// The scope the function was born into.
scope: GcCell<'gc, Scope<'gc>>,
/// The constant pool the function executes with.
constant_pool: GcCell<'gc, Vec<String>>,
}
impl<'gc> Avm1Function<'gc> {
@ -79,6 +83,7 @@ impl<'gc> Avm1Function<'gc> {
name: &str,
params: &[&str],
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<String>>,
) -> Self {
let name = match name {
"" => None,
@ -101,6 +106,7 @@ impl<'gc> Avm1Function<'gc> {
preload_global: false,
params: params.iter().map(|s| (None, s.to_string())).collect(),
scope,
constant_pool,
}
}
@ -110,6 +116,7 @@ impl<'gc> Avm1Function<'gc> {
actions: SwfSlice,
swf_function: &swf::avm1::types::Function,
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<String>>,
) -> Self {
let name = match swf_function.name {
"" => None,
@ -141,6 +148,7 @@ impl<'gc> Avm1Function<'gc> {
preload_global: swf_function.preload_global,
params: owned_params,
scope,
constant_pool,
}
}
@ -223,6 +231,19 @@ impl<'gc> Executable<'gc> {
}
let argcell = arguments.into();
let super_object: Option<Object<'gc>> = if !af.suppress_super {
Some(SuperObject::from_child_object(this, avm, ac)?.into())
} else {
None
};
if let Some(super_object) = super_object {
super_object
.as_super_object()
.unwrap()
.bind_this(ac.gc_context, super_object);
}
let effective_ver = if avm.current_swf_version() > 5 {
af.swf_version()
} else {
@ -237,6 +258,7 @@ impl<'gc> Executable<'gc> {
effective_ver,
af.data(),
child_scope,
af.constant_pool,
this,
Some(argcell),
),
@ -261,12 +283,15 @@ impl<'gc> Executable<'gc> {
preload_r += 1;
}
if let Some(super_object) = super_object {
if af.preload_super {
//TODO: super not implemented
log::warn!("Cannot preload super into register because it's not implemented");
frame.set_local_register(preload_r, super_object, ac.gc_context);
//TODO: What happens if you specify both suppress and
//preload for super?
preload_r += 1;
} else {
frame.define("super", super_object, ac.gc_context);
}
}
if af.preload_root {

View File

@ -232,6 +232,13 @@ pub fn create_globals<'gc>(
EnumSet::empty(),
Some(function_proto),
);
globals.force_set_function(
"ASSetPropFlags",
object::as_set_prop_flags,
gc_context,
EnumSet::empty(),
Some(function_proto),
);
globals.add_property(
gc_context,
"NaN",

View File

@ -15,6 +15,7 @@ pub fn constructor<'gc>(
Ok(Value::Undefined.into())
}
/// Implements `Function.prototype.call`
pub fn call<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
@ -24,6 +25,7 @@ pub fn call<'gc>(
Ok(Value::Undefined.into())
}
/// Implements `Function.prototype.apply`
pub fn apply<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
@ -33,6 +35,16 @@ pub fn apply<'gc>(
Ok(Value::Undefined.into())
}
/// Implements `Function.prototype.toString`
fn to_string<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: Object<'gc>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate("[type Function]".into()))
}
/// Partially construct `Function.prototype`.
///
/// `__proto__` and other cross-linked properties of this object will *not*
@ -51,6 +63,10 @@ pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc
.as_script_object()
.unwrap()
.force_set_function("apply", apply, gc_context, EnumSet::empty(), this);
function_proto
.as_script_object()
.unwrap()
.force_set_function("toString", to_string, gc_context, EnumSet::empty(), this);
function_proto
}

View File

@ -1,5 +1,5 @@
//! Object prototype
use crate::avm1::property::Attribute::*;
use crate::avm1::property::Attribute::{self, *};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
use enumset::EnumSet;
@ -180,3 +180,76 @@ pub fn fill_proto<'gc>(
Some(fn_proto),
);
}
/// Implements `ASSetPropFlags`.
///
/// This is an undocumented function that allows ActionScript 2.0 classes to
/// declare the property flags of a given property. It's not part of
/// `Object.prototype`, and I suspect that's a deliberate omission.
pub fn as_set_prop_flags<'gc>(
avm: &mut Avm1<'gc>,
ac: &mut UpdateContext<'_, 'gc, '_>,
_: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
//This exists because `.into` won't work inline
let my_error: Result<ReturnValue<'gc>, Error> =
Err("ASSetPropFlags called without object to apply to!".into());
let my_error_2: Result<ReturnValue<'gc>, Error> =
Err("ASSetPropFlags called without object list!".into());
let mut object = args
.get(0)
.ok_or_else(|| my_error.unwrap_err())?
.as_object()?;
let properties = match args.get(1).ok_or_else(|| my_error_2.unwrap_err())? {
Value::Object(ob) => {
//Convert to native array.
//TODO: Can we make this an iterator?
let mut array = vec![];
let length = ob
.get("length", avm, ac)?
.resolve(avm, ac)?
.as_number(avm, ac)? as usize;
for i in 0..length {
array.push(
ob.get(&format!("{}", i), avm, ac)?
.resolve(avm, ac)?
.coerce_to_string(avm, ac)?,
)
}
Some(array)
}
Value::String(s) => Some(s.split(',').map(String::from).collect()),
_ => None,
};
let set_attributes = EnumSet::<Attribute>::from_bits(
args.get(2)
.unwrap_or(&Value::Number(0.0))
.as_number(avm, ac)? as u128,
);
let clear_attributes = EnumSet::<Attribute>::from_bits(
args.get(3)
.unwrap_or(&Value::Number(0.0))
.as_number(avm, ac)? as u128,
);
match properties {
Some(properties) => {
for prop_name in properties {
object.set_attributes(
ac.gc_context,
Some(&prop_name),
set_attributes,
clear_attributes,
)
}
}
None => object.set_attributes(ac.gc_context, None, set_attributes, clear_attributes),
}
Ok(Value::Undefined.into())
}

View File

@ -3,6 +3,7 @@
use crate::avm1::function::Executable;
use crate::avm1::property::Attribute;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::super_object::SuperObject;
use crate::avm1::{Avm1, Error, ScriptObject, StageObject, UpdateContext, Value};
use crate::display_object::DisplayObject;
use enumset::EnumSet;
@ -19,6 +20,7 @@ use std::fmt::Debug;
pub enum Object<'gc> {
ScriptObject(ScriptObject<'gc>),
StageObject(StageObject<'gc>),
SuperObject(SuperObject<'gc>),
}
)]
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
@ -124,6 +126,21 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
attributes: EnumSet<Attribute>,
);
/// Set the attributes of a given property.
///
/// Leaving `name` unspecified allows setting all properties on a given
/// object to the same set of properties.
///
/// Attributes can be set, cleared, or left as-is using the pairs of `set_`
/// and `clear_attributes` parameters.
fn set_attributes(
&mut self,
gc_context: MutationContext<'gc, '_>,
name: Option<&str>,
set_attributes: EnumSet<Attribute>,
clear_attributes: EnumSet<Attribute>,
);
/// Define a virtual property onto a given object.
///
/// A virtual property is a set of get/set functions that are called when a
@ -165,9 +182,75 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get the object's type string.
fn type_of(&self) -> &'static str;
/// Enumerate all interfaces implemented by this object.
fn interfaces(&self) -> Vec<Object<'gc>>;
/// Set the interface list for this object. (Only useful for prototypes.)
fn set_interfaces(
&mut self,
gc_context: MutationContext<'gc, '_>,
iface_list: Vec<Object<'gc>>,
);
/// Determine if this object is an instance of a class.
///
/// The class is provided in the form of it's constructor function and the
/// explicit prototype of that constructor function. It is assumed that
/// they are already linked.
///
/// Because ActionScript 2.0 added interfaces, this function cannot simply
/// check the prototype chain and call it a day. Each interface represents
/// a new, parallel prototype chain which also needs to be checked. You
/// can't implement interfaces within interfaces (fortunately), but if you
/// somehow could this would support that, too.
fn is_instance_of(
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
constructor: Object<'gc>,
prototype: Object<'gc>,
) -> Result<bool, Error> {
let mut proto_stack = vec![];
if let Some(p) = self.proto() {
proto_stack.push(p);
}
while let Some(this_proto) = proto_stack.pop() {
if Object::ptr_eq(this_proto, prototype) {
return Ok(true);
}
if let Some(p) = this_proto.proto() {
proto_stack.push(p);
}
if avm.current_swf_version() >= 7 {
for interface in this_proto.interfaces() {
if Object::ptr_eq(interface, constructor) {
return Ok(true);
}
if let Value::Object(o) = interface
.get("prototype", avm, context)?
.resolve(avm, context)?
{
proto_stack.push(o);
}
}
}
}
Ok(false)
}
/// Get the underlying script object, if it exists.
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
/// Get the underlying super object, if it exists.
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
None
}
/// Get the underlying display node for this object, if it exists.
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;

View File

@ -8,10 +8,12 @@ use core::fmt;
use enumset::{EnumSet, EnumSetType};
use std::mem::replace;
/// Attributes of properties in the AVM runtime.
/// The order is significant and should match the order used by `object::as_set_prop_flags`.
#[derive(EnumSetType, Debug)]
pub enum Attribute {
DontDelete,
DontEnum,
DontDelete,
ReadOnly,
}
@ -79,6 +81,26 @@ impl<'gc> Property<'gc> {
}
}
/// List this property's attributes.
pub fn attributes(&self) -> EnumSet<Attribute> {
match self {
Property::Virtual { attributes, .. } => *attributes,
Property::Stored { attributes, .. } => *attributes,
}
}
/// Re-define this property's attributes.
pub fn set_attributes(&mut self, new_attributes: EnumSet<Attribute>) {
match self {
Property::Virtual {
ref mut attributes, ..
} => *attributes = new_attributes,
Property::Stored {
ref mut attributes, ..
} => *attributes = new_attributes,
}
}
pub fn can_delete(&self) -> bool {
match self {
Property::Virtual { attributes, .. } => !attributes.contains(DontDelete),

View File

@ -27,6 +27,7 @@ pub struct ScriptObjectData<'gc> {
prototype: Option<Object<'gc>>,
values: HashMap<String, Property<'gc>>,
function: Option<Executable<'gc>>,
interfaces: Vec<Object<'gc>>,
type_of: &'static str,
array: ArrayStorage<'gc>,
}
@ -37,6 +38,7 @@ unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
self.values.trace(cc);
self.function.trace(cc);
self.array.trace(cc);
self.interfaces.trace(cc);
}
}
@ -64,6 +66,7 @@ impl<'gc> ScriptObject<'gc> {
values: HashMap::new(),
function: None,
array: ArrayStorage::Properties { length: 0 },
interfaces: vec![],
},
))
}
@ -80,6 +83,7 @@ impl<'gc> ScriptObject<'gc> {
values: HashMap::new(),
function: None,
array: ArrayStorage::Vector(Vec::new()),
interfaces: vec![],
},
));
object.sync_native_property("length", gc_context, Some(0.into()));
@ -99,6 +103,7 @@ impl<'gc> ScriptObject<'gc> {
values: HashMap::new(),
function: None,
array: ArrayStorage::Properties { length: 0 },
interfaces: vec![],
},
))
.into()
@ -118,6 +123,7 @@ impl<'gc> ScriptObject<'gc> {
values: HashMap::new(),
function: None,
array: ArrayStorage::Properties { length: 0 },
interfaces: vec![],
},
))
}
@ -136,6 +142,7 @@ impl<'gc> ScriptObject<'gc> {
function: Some(function.into()),
values: HashMap::new(),
array: ArrayStorage::Properties { length: 0 },
interfaces: vec![],
},
))
}
@ -391,6 +398,30 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
.insert(name.to_string(), Property::Stored { value, attributes });
}
fn set_attributes(
&mut self,
gc_context: MutationContext<'gc, '_>,
name: Option<&str>,
set_attributes: EnumSet<Attribute>,
clear_attributes: EnumSet<Attribute>,
) {
match name {
None => {
// Change *all* attributes.
for (_name, prop) in self.0.write(gc_context).values.iter_mut() {
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
prop.set_attributes(new_atts);
}
}
Some(name) => {
if let Some(prop) = self.0.write(gc_context).values.get_mut(name) {
let new_atts = (prop.attributes() - clear_attributes) | set_attributes;
prop.set_attributes(new_atts);
}
}
}
}
fn proto(&self) -> Option<Object<'gc>> {
self.0.read().prototype
}
@ -465,6 +496,14 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
self.0.read().type_of
}
fn interfaces(&self) -> Vec<Object<'gc>> {
self.0.read().interfaces.clone()
}
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
self.0.write(context).interfaces = iface_list;
}
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
Some(*self)
}

View File

@ -150,6 +150,17 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.base.define_value(gc_context, name, value, attributes)
}
fn set_attributes(
&mut self,
gc_context: MutationContext<'gc, '_>,
name: Option<&str>,
set_attributes: EnumSet<Attribute>,
clear_attributes: EnumSet<Attribute>,
) {
self.base
.set_attributes(gc_context, name, set_attributes, clear_attributes)
}
fn add_property(
&self,
gc_context: MutationContext<'gc, '_>,
@ -228,6 +239,14 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.base.delete_array_element(index, gc_context)
}
fn interfaces(&self) -> Vec<Object<'gc>> {
self.base.interfaces()
}
fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>) {
self.base.set_interfaces(context, iface_list)
}
fn as_string(&self) -> String {
self.display_object.path()
}

View File

@ -0,0 +1,252 @@
//! Special object that implements `super`
use crate::avm1::function::Executable;
use crate::avm1::property::Attribute;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::script_object::TYPE_OF_OBJECT;
use crate::avm1::{Avm1, Error, Object, ObjectPtr, ScriptObject, TObject, Value};
use crate::context::UpdateContext;
use crate::display_object::DisplayObject;
use enumset::EnumSet;
use gc_arena::{Collect, GcCell, MutationContext};
use std::collections::HashSet;
/// Implementation of the `super` object in AS2.
///
/// A `SuperObject` references all data from another object, but with one layer
/// of prototyping removed. It's as if the given object had been constructed
/// with it's parent class.
#[collect(no_drop)]
#[derive(Copy, Clone, Collect, Debug)]
pub struct SuperObject<'gc>(GcCell<'gc, SuperObjectData<'gc>>);
#[collect(no_drop)]
#[derive(Clone, Collect, Debug)]
pub struct SuperObjectData<'gc> {
child: Object<'gc>,
proto: Option<Object<'gc>>,
constr: Option<Object<'gc>>,
this: Option<Object<'gc>>,
}
impl<'gc> SuperObject<'gc> {
pub fn from_child_object(
child: Object<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Self, Error> {
let child_proto = child.proto();
let parent_proto = child_proto.and_then(|pr| pr.proto());
let parent_constr = if let Some(child_proto) = child_proto {
Some(
child_proto
.get("constructor", avm, context)?
.resolve(avm, context)?
.as_object()?,
)
} else {
None
};
Ok(Self(GcCell::allocate(
context.gc_context,
SuperObjectData {
child,
proto: parent_proto,
constr: parent_constr,
this: None,
},
)))
}
/// Set `this` to a particular value.
///
/// This is intended to be called with a self-reference, so that future
/// invocations of `super()` can get a `this` value one level up the chain.
pub fn bind_this(&mut self, context: MutationContext<'gc, '_>, this: Object<'gc>) {
self.0.write(context).this = Some(this);
}
}
impl<'gc> TObject<'gc> for SuperObject<'gc> {
fn get_local(
&self,
_name: &str,
_avm: &mut Avm1<'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
}
fn set(
&self,
_name: &str,
_value: Value<'gc>,
_avm: &mut Avm1<'gc>,
_context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
//TODO: What happens if you set `super.__proto__`?
Ok(())
}
fn call(
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(constr) = self.0.read().constr {
constr.call(avm, context, self.0.read().this.unwrap_or(this), args)
} else {
Ok(Value::Undefined.into())
}
}
#[allow(clippy::new_ret_no_self)]
fn new(
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Object<'gc>, Error> {
if let Some(proto) = self.proto() {
proto.new(avm, context, this, args)
} else {
// TODO: What happens when you `new super` but there's no
// super? Is this code even reachable?!
self.0.read().child.new(avm, context, this, args)
}
}
fn delete(&self, _gc_context: MutationContext<'gc, '_>, _name: &str) -> bool {
//`super` cannot have properties deleted from it
false
}
fn proto(&self) -> Option<Object<'gc>> {
self.0.read().proto
}
fn define_value(
&self,
_gc_context: MutationContext<'gc, '_>,
_name: &str,
_value: Value<'gc>,
_attributes: EnumSet<Attribute>,
) {
//`super` cannot have values defined on it
}
fn set_attributes(
&mut self,
_gc_context: MutationContext<'gc, '_>,
_name: Option<&str>,
_set_attributes: EnumSet<Attribute>,
_clear_attributes: EnumSet<Attribute>,
) {
//TODO: Does ASSetPropFlags work on `super`? What would it even work on?
}
fn add_property(
&self,
_gc_context: MutationContext<'gc, '_>,
_name: &str,
_get: Executable<'gc>,
_set: Option<Executable<'gc>>,
_attributes: EnumSet<Attribute>,
) {
//`super` cannot have properties defined on it
}
fn has_property(&self, name: &str) -> bool {
self.0.read().child.has_property(name)
}
fn has_own_property(&self, name: &str) -> bool {
self.0.read().child.has_own_property(name)
}
fn is_property_enumerable(&self, name: &str) -> bool {
self.0.read().child.is_property_enumerable(name)
}
fn is_property_overwritable(&self, name: &str) -> bool {
self.0.read().child.is_property_overwritable(name)
}
fn get_keys(&self) -> HashSet<String> {
//`super` cannot be enumerated
HashSet::new()
}
fn as_string(&self) -> String {
self.0.read().child.as_string()
}
fn type_of(&self) -> &'static str {
TYPE_OF_OBJECT
}
fn length(&self) -> usize {
0
}
fn set_length(&self, _gc_context: MutationContext<'gc, '_>, _new_length: usize) {}
fn array(&self) -> Vec<Value<'gc>> {
vec![]
}
fn array_element(&self, _index: usize) -> Value<'gc> {
Value::Undefined
}
fn set_array_element(
&self,
_index: usize,
_value: Value<'gc>,
_gc_context: MutationContext<'gc, '_>,
) -> usize {
0
}
fn delete_array_element(&self, _index: usize, _gc_context: MutationContext<'gc, '_>) {}
fn interfaces(&self) -> Vec<Object<'gc>> {
//`super` does not implement interfaces
vec![]
}
fn set_interfaces(
&mut self,
_gc_context: MutationContext<'gc, '_>,
_iface_list: Vec<Object<'gc>>,
) {
//`super` probably cannot have interfaces set on it
}
fn as_script_object(&self) -> Option<ScriptObject<'gc>> {
None
}
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
Some(*self)
}
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
//`super` actually can be used to invoke MovieClip methods
self.0.read().child.as_display_object()
}
fn as_executable(&self) -> Option<Executable<'gc>> {
//well, `super` *can* be called...
self.0.read().constr.and_then(|c| c.as_executable())
}
fn as_ptr(&self) -> *const ObjectPtr {
self.0.as_ptr() as *const ObjectPtr
}
}

View File

@ -74,7 +74,7 @@ swf_tests! {
(recursive_prototypes, "avm1/recursive_prototypes", 1),
(stage_object_children, "avm1/stage_object_children", 2),
(has_own_property, "avm1/has_own_property", 1),
#[ignore] (extends_chain, "avm1/extends_chain", 1),
(extends_chain, "avm1/extends_chain", 1),
(is_prototype_of, "avm1/is_prototype_of", 1),
#[ignore] (string_coercion, "avm1/string_coercion", 1),
(lessthan_swf4, "avm1/lessthan_swf4", 1),
@ -89,6 +89,7 @@ swf_tests! {
(equals2_swf7, "avm1/equals2_swf7", 1),
(strictequals_swf6, "avm1/strictequals_swf6", 1),
(global_is_bare, "avm1/global_is_bare", 1),
(as2_oop, "avm1/as2_oop", 1),
}
#[test]

View File

@ -0,0 +1,3 @@
interface MyInterface {
function a();
}

View File

@ -0,0 +1,15 @@
import MyInterface;
class MyObject implements MyInterface, MyOtherInterface {
function a() {
trace("MyObject.a called");
}
function b() {
trace("clock crew's back baby");
}
function c() {
trace("MyObject.c called");
}
}

View File

@ -0,0 +1,3 @@
interface MyOtherInterface {
function b();
}

View File

@ -0,0 +1,3 @@
interface NotMyInterface {
function c();
}

View File

@ -0,0 +1,13 @@
[type Function]
[object Object]
[type Function]
[object Object]
[object Object]
true
true
false
MyObject.a called
clock crew's back baby
MyObject.c called
[object Object]
null

Binary file not shown.

Binary file not shown.