avm1: Use enum_trait_object for avm1::Object

This commit is contained in:
Mike Welsh 2019-12-06 10:24:36 -08:00
parent b27bc578e0
commit 23ca66a7e3
21 changed files with 561 additions and 572 deletions

View File

@ -14,6 +14,7 @@ libflate = "0.1.26"
log = "0.4"
minimp3 = { version = "0.3.3", optional = true }
puremp3 = { version = "0.1", optional = true }
ruffle_macros = { path = "macros" }
swf = { path = "../swf" }
enumset = "0.4.2"
smallvec = "1.0.0"

View File

@ -33,7 +33,7 @@ mod tests;
use activation::Activation;
pub use globals::SystemPrototypes;
pub use object::{Object, ObjectCell};
pub use object::{Object, ObjectPtr, TObject};
use scope::Scope;
pub use script_object::ScriptObject;
pub use value::Value;
@ -53,7 +53,7 @@ pub struct Avm1<'gc> {
constant_pool: Vec<String>,
/// The global object.
globals: ObjectCell<'gc>,
globals: Object<'gc>,
/// System builtins that we use internally to construct new objects.
prototypes: globals::SystemPrototypes<'gc>,
@ -116,16 +116,14 @@ impl<'gc> Avm1<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
) -> HashMap<String, String> {
let mut form_values = HashMap::new();
let locals = self
.current_stack_frame()
.unwrap()
.read()
.scope()
.locals_cell();
let keys = locals.read().get_keys();
let stack_frame = self.current_stack_frame().unwrap();
let stack_frame = stack_frame.read();
let scope = stack_frame.scope();
let locals = scope.locals();
let keys = locals.get_keys();
for k in keys {
let v = locals.read().get(&k, self, context, locals);
let v = locals.get(&k, self, context, *locals);
//TODO: What happens if an error occurs inside a virtual property?
form_values.insert(
@ -159,8 +157,7 @@ impl<'gc> Avm1<'gc> {
.read()
.object()
.as_object()
.unwrap()
.to_owned();
.unwrap();
let child_scope = GcCell::allocate(
action_context.gc_context,
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
@ -187,8 +184,7 @@ impl<'gc> Avm1<'gc> {
.read()
.object()
.as_object()
.unwrap()
.to_owned();
.unwrap();
let child_scope = GcCell::allocate(
action_context.gc_context,
Scope::new(global_scope, scope::ScopeClass::Target, clip_obj),
@ -743,7 +739,7 @@ impl<'gc> Avm1<'gc> {
.read()
.resolve(fn_name.as_string()?, self, context)?
.resolve(self, context)?;
let this = context.active_clip.read().object().as_object()?.to_owned();
let this = context.active_clip.read().object().as_object()?;
target_fn.call(self, context, this, &args)?.push(self);
Ok(())
@ -763,19 +759,18 @@ impl<'gc> Avm1<'gc> {
match method_name {
Value::Undefined | Value::Null => {
let this = context.active_clip.read().object().as_object()?.to_owned();
let this = context.active_clip.read().object().as_object()?;
object.call(self, context, this, &args)?.push(self);
}
Value::String(name) => {
if name.is_empty() {
object
.call(self, context, object.as_object()?.to_owned(), &args)?
.call(self, context, object.as_object()?, &args)?
.push(self);
} else {
let target = object.as_object()?.to_owned();
let target = object.as_object()?;
let callable = object
.as_object()?
.read()
.get(&name, self, context, target)?
.resolve(self, context)?;
@ -834,13 +829,8 @@ impl<'gc> Avm1<'gc> {
context.gc_context,
);
let func = Avm1Function::from_df1(swf_version, func_data, name, params, scope);
let prototype = GcCell::allocate(
context.gc_context,
Box::new(ScriptObject::object(
context.gc_context,
Some(self.prototypes.object),
)) as Box<dyn Object<'gc>>,
);
let prototype =
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
let func_obj = ScriptObject::function(
context.gc_context,
func,
@ -877,13 +867,8 @@ impl<'gc> Avm1<'gc> {
context.gc_context,
);
let func = Avm1Function::from_df2(swf_version, func_data, action_func, scope);
let prototype = GcCell::allocate(
context.gc_context,
Box::new(ScriptObject::object(
context.gc_context,
Some(self.prototypes.object),
)) as Box<dyn Object<'gc>>,
);
let prototype =
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
let func_obj = ScriptObject::function(
context.gc_context,
func,
@ -935,7 +920,7 @@ impl<'gc> Avm1<'gc> {
let name = name_val.as_string()?;
let object = self.pop()?.as_object()?;
let success = object.write(context.gc_context).delete(name);
let success = object.delete(context.gc_context, name);
self.push(success);
Ok(())
@ -991,7 +976,7 @@ impl<'gc> Avm1<'gc> {
match object {
Value::Object(ob) => {
for k in ob.read().get_keys() {
for k in ob.get_keys() {
self.push(k);
}
}
@ -1008,7 +993,7 @@ impl<'gc> Avm1<'gc> {
let object = self.pop()?.as_object()?;
self.push(Value::Null); // Sentinel that indicates end of enumeration
for k in object.read().get_keys() {
for k in object.get_keys() {
self.push(k);
}
@ -1039,7 +1024,7 @@ impl<'gc> Avm1<'gc> {
let name_val = self.pop()?;
let name = name_val.coerce_to_string(self, context)?;
let object = self.pop()?.as_object()?;
object.read().get(&name, self, context, object)?.push(self);
object.get(&name, self, context, object)?.push(self);
Ok(())
}
@ -1107,7 +1092,7 @@ impl<'gc> Avm1<'gc> {
}
/// Obtain a reference to `_global`.
pub fn global_object_cell(&self) -> ObjectCell<'gc> {
pub fn global_object_cell(&self) -> Object<'gc> {
self.globals
}
@ -1130,11 +1115,8 @@ impl<'gc> Avm1<'gc> {
{
if let Some(clip) = node.read().as_movie_clip() {
let object = clip.object().as_object()?;
if object.read().has_property(var_name) {
object
.read()
.get(var_name, self, context, object)?
.push(self);
if object.has_property(var_name) {
object.get(var_name, self, context, object)?.push(self);
} else {
self.push(Value::Undefined);
}
@ -1330,7 +1312,7 @@ impl<'gc> Avm1<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let num_props = self.pop()?.as_i64()?;
let mut object = ScriptObject::object(context.gc_context, Some(self.prototypes.object));
let object = ScriptObject::object(context.gc_context, Some(self.prototypes.object));
for _ in 0..num_props {
let value = self.pop()?;
let name = self.pop()?.into_string();
@ -1339,10 +1321,7 @@ impl<'gc> Avm1<'gc> {
object.set(&name, value, self, context, this)?;
}
self.push(Value::Object(GcCell::allocate(
context.gc_context,
Box::new(object),
)));
self.push(Value::Object(object.into()));
Ok(())
}
@ -1356,19 +1335,18 @@ impl<'gc> Avm1<'gc> {
//TODO: Interface detection on SWF7
let prototype = constr
.read()
.get("prototype", self, context, constr)?
.resolve(self, context)?
.as_object()?;
let mut proto = obj.read().proto();
let mut proto = obj.proto();
while let Some(this_proto) = proto {
if GcCell::ptr_eq(this_proto, prototype) {
if Object::ptr_eq(this_proto, prototype) {
self.push(true);
return Ok(());
}
proto = this_proto.read().proto();
proto = this_proto.proto();
}
self.push(false);
@ -1498,21 +1476,18 @@ impl<'gc> Avm1<'gc> {
}
let constructor = object
.read()
.get(&method_name.as_string()?, self, context, object.to_owned())?
.get(&method_name.as_string()?, self, context, object)?
.resolve(self, context)?
.as_object()?;
let prototype = constructor
.read()
.get("prototype", self, context, constructor)?
.resolve(self, context)?
.as_object()?;
let this = prototype.read().new(self, context, prototype, &args)?;
let this = prototype.new(self, context, prototype, &args)?;
//TODO: What happens if you `ActionNewMethod` without a method name?
constructor
.read()
.call(self, context, this, &args)?
.resolve(self, context)?;
@ -1539,15 +1514,13 @@ impl<'gc> Avm1<'gc> {
.resolve(self, context)?
.as_object()?;
let prototype = constructor
.read()
.get("prototype", self, context, constructor)?
.resolve(self, context)?
.as_object()?;
let this = prototype.read().new(self, context, prototype, &args)?;
let this = prototype.new(self, context, prototype, &args)?;
constructor
.read()
.call(self, context, this, &args)?
.resolve(self, context)?;
@ -1667,9 +1640,7 @@ impl<'gc> Avm1<'gc> {
let name = name_val.coerce_to_string(self, context)?;
let object = self.pop()?.as_object()?;
object
.write(context.gc_context)
.set(&name, value, self, context, object)?;
object.set(&name, value, self, context, object)?;
Ok(())
}
@ -1735,13 +1706,9 @@ impl<'gc> Avm1<'gc> {
Self::resolve_slash_path_variable(context.target_clip, context.root, var_path)
{
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
clip.object().as_object()?.write(context.gc_context).set(
var_name,
value.clone(),
self,
context,
this,
)?;
clip.object()
.as_object()?
.set(var_name, value.clone(), self, context, this)?;
}
}
} else {
@ -1791,13 +1758,7 @@ impl<'gc> Avm1<'gc> {
}
let scope = self.current_stack_frame().unwrap().read().scope_cell();
let clip_obj = context
.active_clip
.read()
.object()
.as_object()
.unwrap()
.to_owned();
let clip_obj = context.active_clip.read().object().as_object().unwrap();
self.current_stack_frame()
.unwrap()

View File

@ -2,7 +2,7 @@
use crate::avm1::return_value::ReturnValue;
use crate::avm1::scope::Scope;
use crate::avm1::{Avm1, Error, ObjectCell, Value};
use crate::avm1::{Avm1, Error, Object, Value};
use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice;
use gc_arena::{GcCell, MutationContext};
@ -67,10 +67,10 @@ pub struct Activation<'gc> {
scope: GcCell<'gc, Scope<'gc>>,
/// The immutable value of `this`.
this: ObjectCell<'gc>,
this: Object<'gc>,
/// The arguments this function was called by.
arguments: Option<ObjectCell<'gc>>,
arguments: Option<Object<'gc>>,
/// The return value of the activation.
return_value: Option<Value<'gc>>,
@ -114,8 +114,8 @@ impl<'gc> Activation<'gc> {
swf_version: u8,
code: SwfSlice,
scope: GcCell<'gc, Scope<'gc>>,
this: ObjectCell<'gc>,
arguments: Option<ObjectCell<'gc>>,
this: Object<'gc>,
arguments: Option<Object<'gc>>,
) -> Activation<'gc> {
Activation {
swf_version,
@ -135,8 +135,8 @@ impl<'gc> Activation<'gc> {
swf_version: u8,
code: SwfSlice,
scope: GcCell<'gc, Scope<'gc>>,
this: ObjectCell<'gc>,
arguments: Option<ObjectCell<'gc>>,
this: Object<'gc>,
arguments: Option<Object<'gc>>,
) -> Activation<'gc> {
Activation {
swf_version,
@ -158,9 +158,10 @@ impl<'gc> Activation<'gc> {
/// will prevent the AVM from panicking without a current activation.
/// We construct a single scope chain from a global object, and that's about
/// it.
#[cfg(test)]
pub fn from_nothing(
swf_version: u8,
globals: ObjectCell<'gc>,
globals: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Activation<'gc> {
let global_scope = GcCell::allocate(mc, Scope::from_global_object(globals));
@ -211,12 +212,14 @@ impl<'gc> Activation<'gc> {
}
/// Change the data being executed.
#[allow(dead_code)]
pub fn set_data(&mut self, new_data: SwfSlice) {
self.data = new_data;
}
/// Determines if a stack frame references the same function as a given
/// SwfSlice.
#[allow(dead_code)]
pub fn is_identical_fn(&self, other: &SwfSlice) -> bool {
Arc::ptr_eq(&self.data.data, &other.data)
}
@ -236,6 +239,7 @@ impl<'gc> Activation<'gc> {
}
/// Returns AVM local variable scope for mutation.
#[allow(dead_code)]
pub fn scope_mut(&mut self, mc: MutationContext<'gc, '_>) -> RefMut<Scope<'gc>> {
self.scope.write(mc)
}
@ -252,6 +256,7 @@ impl<'gc> Activation<'gc> {
/// Indicates whether or not the end of this scope should be handled as an
/// implicit function return or the end of a block.
#[allow(dead_code)]
pub fn can_implicit_return(&self) -> bool {
self.is_function
}
@ -296,7 +301,7 @@ impl<'gc> Activation<'gc> {
}
/// Returns value of `this` as a reference.
pub fn this_cell(&self) -> ObjectCell<'gc> {
pub fn this_cell(&self) -> Object<'gc> {
self.this
}
@ -358,6 +363,7 @@ impl<'gc> Activation<'gc> {
/// Retrieve the return value from a completed activation, if the function
/// has already returned.
#[allow(dead_code)]
pub fn return_value(&self) -> Option<Value<'gc>> {
self.return_value.clone()
}

View File

@ -5,7 +5,7 @@ use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::scope::Scope;
use crate::avm1::value::Value;
use crate::avm1::{Avm1, Error, Object, ObjectCell, ScriptObject, UpdateContext};
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext};
use crate::tag_utils::SwfSlice;
use gc_arena::{Collect, CollectionContext, GcCell};
use swf::avm1::types::FunctionParam;
@ -27,7 +27,7 @@ use swf::avm1::types::FunctionParam;
pub type NativeFunction<'gc> = fn(
&mut Avm1<'gc>,
&mut UpdateContext<'_, 'gc, '_>,
ObjectCell<'gc>,
Object<'gc>,
&[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error>;
@ -192,7 +192,7 @@ impl<'gc> Executable<'gc> {
&self,
avm: &mut Avm1<'gc>,
ac: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match self {
@ -202,29 +202,30 @@ impl<'gc> Executable<'gc> {
ac.gc_context,
Scope::new_local_scope(af.scope(), ac.gc_context),
);
let mut arguments =
ScriptObject::object(ac.gc_context, Some(avm.prototypes().object));
let arguments = ScriptObject::object(ac.gc_context, Some(avm.prototypes().object));
if !af.suppress_arguments {
for i in 0..args.len() {
arguments.define_value(
ac.gc_context,
&format!("{}", i),
args.get(i).unwrap().clone(),
DontDelete.into(),
)
}
arguments.define_value("length", args.len().into(), DontDelete | DontEnum);
arguments.define_value(
ac.gc_context,
"length",
args.len().into(),
DontDelete | DontEnum,
);
}
let argcell = GcCell::allocate(
ac.gc_context,
Box::new(arguments) as Box<dyn Object<'gc> + 'gc>,
);
let argcell = arguments.into();
let effective_ver = if avm.current_swf_version() > 5 {
af.swf_version()
} else {
this.read()
.as_display_node()
this.as_display_node()
.map(|dn| dn.read().swf_version())
.unwrap_or(ac.player_version)
};

View File

@ -1,10 +1,10 @@
use crate::avm1::fscommand;
use crate::avm1::function::Executable;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ObjectCell, ScriptObject, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
use crate::backend::navigator::NavigationMethod;
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use gc_arena::MutationContext;
use rand::Rng;
use std::f64;
@ -17,7 +17,7 @@ mod object;
pub fn getURL<'a, 'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'a, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
//TODO: Error behavior if no arguments are present
@ -45,7 +45,7 @@ pub fn getURL<'a, 'gc>(
pub fn random<'gc>(
_avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match args.get(0) {
@ -57,7 +57,7 @@ pub fn random<'gc>(
pub fn boolean<'gc>(
avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(val) = args.get(0) {
@ -70,7 +70,7 @@ pub fn boolean<'gc>(
pub fn number<'gc>(
avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(val) = args.get(0) {
@ -83,7 +83,7 @@ pub fn number<'gc>(
pub fn is_nan<'gc>(
avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(val) = args.get(0) {
@ -96,7 +96,7 @@ pub fn is_nan<'gc>(
pub fn get_infinity<'gc>(
avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if avm.current_swf_version() > 4 {
@ -109,7 +109,7 @@ pub fn get_infinity<'gc>(
pub fn get_nan<'gc>(
avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if avm.current_swf_version() > 4 {
@ -124,9 +124,9 @@ pub fn get_nan<'gc>(
/// user-modifiable.
#[derive(Clone)]
pub struct SystemPrototypes<'gc> {
pub object: ObjectCell<'gc>,
pub function: ObjectCell<'gc>,
pub movie_clip: ObjectCell<'gc>,
pub object: Object<'gc>,
pub function: Object<'gc>,
pub movie_clip: Object<'gc>,
}
unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
@ -141,16 +141,13 @@ unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
/// Initialize default global scope and builtins for an AVM1 instance.
pub fn create_globals<'gc>(
gc_context: MutationContext<'gc, '_>,
) -> (
SystemPrototypes<'gc>,
GcCell<'gc, Box<dyn Object<'gc> + 'gc>>,
) {
) -> (SystemPrototypes<'gc>, Object<'gc>) {
let object_proto = ScriptObject::object_cell(gc_context, None);
let function_proto = function::create_proto(gc_context, object_proto);
object::fill_proto(gc_context, object_proto, function_proto);
let movie_clip_proto: ObjectCell<'gc> =
let movie_clip_proto: Object<'gc> =
movie_clip::create_proto(gc_context, object_proto, function_proto);
//TODO: These need to be constructors and should also set `.prototype` on each one
@ -174,10 +171,10 @@ pub fn create_globals<'gc>(
Some(movie_clip_proto),
);
let mut globals = ScriptObject::bare_object();
globals.define_value("Object", object.into(), EnumSet::empty());
globals.define_value("Function", function.into(), EnumSet::empty());
globals.define_value("MovieClip", movie_clip.into(), EnumSet::empty());
let mut globals = ScriptObject::bare_object(gc_context);
globals.define_value(gc_context, "Object", object.into(), EnumSet::empty());
globals.define_value(gc_context, "Function", function.into(), EnumSet::empty());
globals.define_value(gc_context, "MovieClip", movie_clip.into(), EnumSet::empty());
globals.force_set_function(
"Number",
number,
@ -193,6 +190,7 @@ pub fn create_globals<'gc>(
Some(function_proto),
);
globals.define_value(
gc_context,
"Math",
Value::Object(math::create(
gc_context,
@ -222,8 +220,15 @@ pub fn create_globals<'gc>(
EnumSet::empty(),
Some(function_proto),
);
globals.add_property("NaN", Executable::Native(get_nan), None, EnumSet::empty());
globals.add_property(
gc_context,
"NaN",
Executable::Native(get_nan),
None,
EnumSet::empty(),
);
globals.add_property(
gc_context,
"Infinity",
Executable::Native(get_infinity),
None,
@ -236,7 +241,7 @@ pub fn create_globals<'gc>(
function: function_proto,
movie_clip: movie_clip_proto,
},
GcCell::allocate(gc_context, Box::new(globals)),
globals.into(),
)
}
@ -245,10 +250,7 @@ pub fn create_globals<'gc>(
mod tests {
use super::*;
fn setup<'gc>(
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> ObjectCell<'gc> {
fn setup<'gc>(_avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) -> Object<'gc> {
create_globals(context.gc_context).1
}

View File

@ -1,7 +1,7 @@
//! Function prototype
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, ObjectCell, ScriptObject, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
use enumset::EnumSet;
use gc_arena::MutationContext;
@ -9,7 +9,7 @@ use gc_arena::MutationContext;
pub fn constructor<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -18,7 +18,7 @@ pub fn constructor<'gc>(
pub fn call<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -27,7 +27,7 @@ pub fn call<'gc>(
pub fn apply<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -40,34 +40,17 @@ pub fn apply<'gc>(
/// them in order to obtain a valid ECMAScript `Function` prototype. The
/// returned object is also a bare object, which will need to be linked into
/// the prototype of `Object`.
pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: ObjectCell<'gc>,
) -> ObjectCell<'gc> {
let function_proto = ScriptObject::object_cell(gc_context, Some(proto));
pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
let mut function_proto = ScriptObject::object_cell(gc_context, Some(proto));
let this = Some(function_proto);
function_proto
.write(gc_context)
.as_script_object_mut()
.unwrap()
.force_set_function(
"call",
call,
gc_context,
EnumSet::empty(),
Some(function_proto),
);
.force_set_function("call", call, gc_context, EnumSet::empty(), this);
function_proto
.write(gc_context)
.as_script_object_mut()
.unwrap()
.force_set_function(
"apply",
apply,
gc_context,
EnumSet::empty(),
Some(function_proto),
);
.force_set_function("apply", apply, gc_context, EnumSet::empty(), this);
function_proto
}

View File

@ -1,8 +1,8 @@
use crate::avm1::object::ObjectCell;
use crate::avm1::object::Object;
use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ScriptObject, UpdateContext, Value};
use gc_arena::{GcCell, MutationContext};
use crate::avm1::{Avm1, Error, ScriptObject, TObject, UpdateContext, Value};
use gc_arena::MutationContext;
use rand::Rng;
use std::f64::NAN;
@ -29,7 +29,7 @@ macro_rules! wrap_std {
fn atan2<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(y) = args.get(0) {
@ -48,7 +48,7 @@ fn atan2<'gc>(
pub fn random<'gc>(
_avm: &mut Avm1<'gc>,
action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(action_context.rng.gen_range(0.0f64, 1.0f64).into())
@ -56,47 +56,55 @@ pub fn random<'gc>(
pub fn create<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: Option<ObjectCell<'gc>>,
fn_proto: Option<ObjectCell<'gc>>,
) -> ObjectCell<'gc> {
proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Object<'gc> {
let mut math = ScriptObject::object(gc_context, proto);
math.define_value(
gc_context,
"E",
std::f64::consts::E.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"LN10",
std::f64::consts::LN_10.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"LN2",
std::f64::consts::LN_2.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"LOG10E",
std::f64::consts::LOG10_E.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"LOG2E",
std::f64::consts::LOG2_E.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"PI",
std::f64::consts::PI.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"SQRT1_2",
std::f64::consts::FRAC_1_SQRT_2.into(),
DontDelete | ReadOnly | DontEnum,
);
math.define_value(
gc_context,
"SQRT2",
std::f64::consts::SQRT_2.into(),
DontDelete | ReadOnly | DontEnum,
@ -132,7 +140,7 @@ pub fn create<'gc>(
fn_proto,
);
GcCell::allocate(gc_context, Box::new(math))
math.into()
}
#[cfg(test)]
@ -140,10 +148,7 @@ mod tests {
use super::*;
use crate::avm1::test_utils::with_avm;
fn setup<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> ObjectCell<'gc> {
fn setup<'gc>(avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>) -> Object<'gc> {
create(
context.gc_context,
Some(avm.prototypes().object),
@ -274,26 +279,23 @@ mod tests {
#[test]
fn test_atan2_nan() {
with_avm(19, |avm, context, _root| {
let math = GcCell::allocate(
let math = create(
context.gc_context,
create(
context.gc_context,
Some(avm.prototypes().object),
Some(avm.prototypes().function),
),
Some(avm.prototypes().object),
Some(avm.prototypes().function),
);
assert_eq!(atan2(avm, context, *math.read(), &[]).unwrap(), NAN.into());
assert_eq!(atan2(avm, context, math, &[]).unwrap(), NAN.into());
assert_eq!(
atan2(avm, context, *math.read(), &[1.0.into(), Value::Null]).unwrap(),
atan2(avm, context, math, &[1.0.into(), Value::Null]).unwrap(),
NAN.into()
);
assert_eq!(
atan2(avm, context, *math.read(), &[1.0.into(), Value::Undefined]).unwrap(),
atan2(avm, context, math, &[1.0.into(), Value::Undefined]).unwrap(),
NAN.into()
);
assert_eq!(
atan2(avm, context, *math.read(), &[Value::Undefined, 1.0.into()]).unwrap(),
atan2(avm, context, math, &[Value::Undefined, 1.0.into()]).unwrap(),
NAN.into()
);
});
@ -302,21 +304,18 @@ mod tests {
#[test]
fn test_atan2_valid() {
with_avm(19, |avm, context, _root| {
let math = GcCell::allocate(
let math = create(
context.gc_context,
create(
context.gc_context,
Some(avm.prototypes().object),
Some(avm.prototypes().function),
),
Some(avm.prototypes().object),
Some(avm.prototypes().function),
);
assert_eq!(
atan2(avm, context, *math.read(), &[10.0.into()]).unwrap(),
atan2(avm, context, math, &[10.0.into()]).unwrap(),
std::f64::consts::FRAC_PI_2.into()
);
assert_eq!(
atan2(avm, context, *math.read(), &[1.0.into(), 2.0.into()]).unwrap(),
atan2(avm, context, math, &[1.0.into(), 2.0.into()]).unwrap(),
f64::atan2(1.0, 2.0).into()
);
});

View File

@ -3,16 +3,16 @@
use crate::avm1::function::Executable;
use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ObjectCell, ScriptObject, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
use crate::display_object::{DisplayNode, DisplayObject, MovieClip};
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use gc_arena::MutationContext;
/// Implements `MovieClip`
pub fn constructor<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -24,7 +24,7 @@ macro_rules! with_movie_clip {
$object.force_set_function(
$name,
|_avm, _context, this, args| -> Result<ReturnValue<'gc>, Error> {
if let Some(display_object) = this.read().as_display_node() {
if let Some(display_object) = this.as_display_node() {
if let Some(movie_clip) = display_object.read().as_movie_clip() {
return Ok($fn(movie_clip, args));
}
@ -45,7 +45,7 @@ macro_rules! with_movie_clip_mut {
$object.force_set_function(
$name,
|_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Result<ReturnValue<'gc>, Error> {
if let Some(display_object) = this.read().as_display_node() {
if let Some(display_object) = this.as_display_node() {
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
return Ok($fn(movie_clip, context, display_object, args).into());
}
@ -63,15 +63,14 @@ macro_rules! with_movie_clip_mut {
pub fn overwrite_root<'gc>(
_avm: &mut Avm1<'gc>,
ac: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let new_val = args
.get(0)
.map(|v| v.to_owned())
.unwrap_or(Value::Undefined);
this.write(ac.gc_context)
.define_value("_root", new_val, EnumSet::new());
this.define_value(ac.gc_context, "_root", new_val, EnumSet::new());
Ok(Value::Undefined.into())
}
@ -79,24 +78,23 @@ pub fn overwrite_root<'gc>(
pub fn overwrite_global<'gc>(
_avm: &mut Avm1<'gc>,
ac: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let new_val = args
.get(0)
.map(|v| v.to_owned())
.unwrap_or(Value::Undefined);
this.write(ac.gc_context)
.define_value("_global", new_val, EnumSet::new());
this.define_value(ac.gc_context, "_global", new_val, EnumSet::new());
Ok(Value::Undefined.into())
}
pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: ObjectCell<'gc>,
fn_proto: ObjectCell<'gc>,
) -> ObjectCell<'gc> {
proto: Object<'gc>,
fn_proto: Object<'gc>,
) -> Object<'gc> {
let mut object = ScriptObject::object(gc_context, Some(proto));
with_movie_clip_mut!(
@ -139,6 +137,7 @@ pub fn create_proto<'gc>(
);
object.add_property(
gc_context,
"_global",
Executable::Native(|avm, context, _this, _args| Ok(avm.global_object(context).into())),
Some(Executable::Native(overwrite_global)),
@ -146,6 +145,7 @@ pub fn create_proto<'gc>(
);
object.add_property(
gc_context,
"_root",
Executable::Native(|avm, context, _this, _args| Ok(avm.root_object(context).into())),
Some(Executable::Native(overwrite_root)),
@ -153,14 +153,14 @@ pub fn create_proto<'gc>(
);
object.add_property(
gc_context,
"_parent",
Executable::Native(|_avm, _context, this, _args| {
Ok(this
.read()
.as_display_node()
.and_then(|mc| mc.read().parent())
.and_then(|dn| dn.read().object().as_object().ok())
.map(|o| Value::Object(o.to_owned()))
.map(Value::Object)
.unwrap_or(Value::Undefined)
.into())
}),
@ -168,5 +168,5 @@ pub fn create_proto<'gc>(
EnumSet::new(),
);
GcCell::allocate(gc_context, Box::new(object))
object.into()
}

View File

@ -1,15 +1,15 @@
//! Object prototype
use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, ObjectCell, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext, Value};
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use gc_arena::MutationContext;
/// Implements `Object`
pub fn constructor<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: ObjectCell<'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
@ -19,7 +19,7 @@ pub fn constructor<'gc>(
pub fn add_property<'gc>(
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let name = args.get(0).unwrap_or(&Value::Undefined);
@ -28,10 +28,11 @@ pub fn add_property<'gc>(
match (name, getter) {
(Value::String(name), Value::Object(get)) if !name.is_empty() => {
if let Some(get_func) = get.read().as_executable() {
if let Some(get_func) = get.as_executable() {
if let Value::Object(set) = setter {
if let Some(set_func) = set.read().as_executable() {
this.write(context.gc_context).add_property(
if let Some(set_func) = set.as_executable() {
this.add_property(
context.gc_context,
name,
get_func,
Some(set_func),
@ -41,12 +42,7 @@ pub fn add_property<'gc>(
return Ok(false.into());
}
} else if let Value::Null = setter {
this.write(context.gc_context).add_property(
name,
get_func,
None,
ReadOnly.into(),
);
this.add_property(context.gc_context, name, get_func, None, ReadOnly.into());
} else {
return Ok(false.into());
}
@ -62,11 +58,11 @@ pub fn add_property<'gc>(
pub fn has_own_property<'gc>(
_avm: &mut Avm1<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match args.get(0) {
Some(Value::String(name)) => Ok(Value::Bool(this.read().has_own_property(name)).into()),
Some(Value::String(name)) => Ok(Value::Bool(this.has_own_property(name)).into()),
_ => Ok(Value::Bool(false).into()),
}
}
@ -75,7 +71,7 @@ pub fn has_own_property<'gc>(
fn to_string<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: ObjectCell<'gc>,
_: Object<'gc>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate("[object Object]".into()))
@ -85,13 +81,11 @@ fn to_string<'gc>(
fn is_property_enumerable<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match args.get(0) {
Some(Value::String(name)) => {
Ok(Value::Bool(this.read().is_property_enumerable(name)).into())
}
Some(Value::String(name)) => Ok(Value::Bool(this.is_property_enumerable(name)).into()),
_ => Ok(Value::Bool(false).into()),
}
}
@ -100,7 +94,7 @@ fn is_property_enumerable<'gc>(
fn is_prototype_of<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
match args.get(0) {
@ -109,14 +103,14 @@ fn is_prototype_of<'gc>(
Ok(ob) => ob,
Err(_) => return Ok(Value::Bool(false).into()),
};
let mut proto = ob.read().proto();
let mut proto = ob.proto();
while let Some(proto_ob) = proto {
if GcCell::ptr_eq(this, proto_ob) {
if Object::ptr_eq(this, proto_ob) {
return Ok(Value::Bool(true).into());
}
proto = proto_ob.read().proto();
proto = proto_ob.proto();
}
Ok(Value::Bool(false).into())
@ -129,7 +123,7 @@ fn is_prototype_of<'gc>(
fn value_of<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate(this.into()))
@ -146,12 +140,10 @@ fn value_of<'gc>(
/// bare objects for both and let this function fill Object for you.
pub fn fill_proto<'gc>(
gc_context: MutationContext<'gc, '_>,
object_proto: ObjectCell<'gc>,
fn_proto: ObjectCell<'gc>,
mut object_proto: Object<'gc>,
fn_proto: Object<'gc>,
) {
let mut ob_proto_write = object_proto.write(gc_context);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(
@ -161,7 +153,7 @@ pub fn fill_proto<'gc>(
DontDelete | DontEnum,
Some(fn_proto),
);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(
@ -171,7 +163,7 @@ pub fn fill_proto<'gc>(
DontDelete | DontEnum,
Some(fn_proto),
);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(
@ -181,7 +173,7 @@ pub fn fill_proto<'gc>(
DontDelete | DontEnum,
Some(fn_proto),
);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(
@ -191,7 +183,7 @@ pub fn fill_proto<'gc>(
DontDelete | DontEnum,
Some(fn_proto),
);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(
@ -201,7 +193,7 @@ pub fn fill_proto<'gc>(
DontDelete | DontEnum,
Some(fn_proto),
);
ob_proto_write
object_proto
.as_script_object_mut()
.unwrap()
.force_set_function(

View File

@ -6,15 +6,21 @@ use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, ScriptObject, UpdateContext, Value};
use crate::display_object::DisplayNode;
use enumset::EnumSet;
use gc_arena::{Collect, GcCell};
use gc_arena::{Collect, MutationContext};
use ruffle_macros::enum_trait_object;
use std::collections::HashSet;
use std::fmt::Debug;
pub type ObjectCell<'gc> = GcCell<'gc, Box<dyn Object<'gc> + 'gc>>;
/// Represents an object that can be directly interacted with by the AVM
/// runtime.
pub trait Object<'gc>: 'gc + Collect + Debug {
#[enum_trait_object(
#[derive(Clone, Collect, Debug, Copy)]
#[collect(no_drop)]
pub enum Object<'gc> {
ScriptObject(ScriptObject<'gc>),
}
)]
pub trait TObject<'gc>: 'gc + Collect + Debug {
/// Retrieve a named property from this object exclusively.
///
/// This function takes a redundant `this` parameter which should be
@ -28,7 +34,7 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
name: &str,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error>;
/// Retrieve a named property from the object, or it's prototype.
@ -37,7 +43,7 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
name: &str,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
if self.has_own_property(name) {
self.get_local(name, avm, context, this)
@ -50,11 +56,11 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
return Err("Encountered an excessively deep prototype chain.".into());
}
if proto.unwrap().read().has_own_property(name) {
return proto.unwrap().read().get_local(name, avm, context, this);
if proto.unwrap().has_own_property(name) {
return proto.unwrap().get_local(name, avm, context, this);
}
proto = proto.unwrap().read().proto();
proto = proto.unwrap().proto();
depth += 1;
}
@ -68,12 +74,12 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
/// the object's own `GcCell`, so that it can pass it to user-defined
/// overrides that may need to interact with the underlying object.
fn set(
&mut self,
&self,
name: &str,
value: Value<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<(), Error>;
/// Call the underlying object.
@ -85,7 +91,7 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error>;
@ -104,21 +110,21 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ObjectCell<'gc>, Error>;
) -> Result<Object<'gc>, Error>;
/// Delete a named property from the object.
///
/// Returns false if the property cannot be deleted.
fn delete(&mut self, name: &str) -> bool;
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool;
/// Retrieve the `__proto__` of a given object.
///
/// The proto is another object used to resolve methods across a class of
/// multiple objects. It should also be accessible as `__proto__` from
/// `get`.
fn proto(&self) -> Option<ObjectCell<'gc>>;
fn proto(&self) -> Option<Object<'gc>>;
/// Define a value on an object.
///
@ -131,7 +137,13 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
/// It is not guaranteed that all objects accept value definitions,
/// especially if a property name conflicts with a built-in property, such
/// as `__proto__`.
fn define_value(&mut self, name: &str, value: Value<'gc>, attributes: EnumSet<Attribute>);
fn define_value(
&self,
gc_context: MutationContext<'gc, '_>,
name: &str,
value: Value<'gc>,
attributes: EnumSet<Attribute>,
);
/// Define a virtual property onto a given object.
///
@ -144,7 +156,8 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
/// especially if a property name conflicts with a built-in property, such
/// as `__proto__`.
fn add_property(
&mut self,
&self,
gc_context: MutationContext<'gc, '_>,
name: &str,
get: Executable<'gc>,
set: Option<Executable<'gc>>,
@ -184,4 +197,14 @@ pub trait Object<'gc>: 'gc + Collect + Debug {
/// Get the underlying executable for this object, if it exists.
fn as_executable(&self) -> Option<Executable<'gc>>;
fn as_ptr(&self) -> *const ObjectPtr;
}
pub enum ObjectPtr {}
impl<'gc> Object<'gc> {
pub fn ptr_eq(a: Object<'gc>, b: Object<'gc>) -> bool {
a.as_ptr() == b.as_ptr()
}
}

View File

@ -3,7 +3,7 @@
use self::Attribute::*;
use crate::avm1::function::Executable;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, ObjectCell, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, UpdateContext, Value};
use core::fmt;
use enumset::{EnumSet, EnumSetType};
use std::mem::replace;
@ -37,7 +37,7 @@ impl<'gc> Property<'gc> {
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
match self {
Property::Virtual { get, .. } => get.exec(avm, context, this, &[]),
@ -55,7 +55,7 @@ impl<'gc> Property<'gc> {
&mut self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
new_value: impl Into<Value<'gc>>,
) -> Result<bool, Error> {
match self {

View File

@ -1,7 +1,7 @@
//! Return value enum
use crate::avm1::activation::Activation;
use crate::avm1::{Avm1, Error, ObjectCell, Value};
use crate::avm1::{Avm1, Error, Object, Value};
use crate::context::UpdateContext;
use gc_arena::{Collect, GcCell};
use std::fmt;
@ -112,6 +112,7 @@ impl<'gc> ReturnValue<'gc> {
/// Panic if a value is not immediate.
///
/// This should only be used in test assertions.
#[cfg(test)]
pub fn unwrap_immediate(self) -> Value<'gc> {
use ReturnValue::*;
@ -146,8 +147,8 @@ impl<'gc> From<bool> for ReturnValue<'gc> {
}
}
impl<'gc> From<ObjectCell<'gc>> for ReturnValue<'gc> {
fn from(object: ObjectCell<'gc>) -> Self {
impl<'gc> From<Object<'gc>> for ReturnValue<'gc> {
fn from(object: Object<'gc>) -> Self {
ReturnValue::Immediate(Value::Object(object))
}
}

View File

@ -1,10 +1,10 @@
//! Represents AVM1 scope chain resolution.
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ObjectCell, ScriptObject, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use std::cell::{Ref, RefMut};
use std::cell::Ref;
/// Indicates what kind of scope a scope is.
#[derive(Copy, Clone, Debug, PartialEq)]
@ -30,7 +30,7 @@ pub enum ScopeClass {
pub struct Scope<'gc> {
parent: Option<GcCell<'gc, Scope<'gc>>>,
class: ScopeClass,
values: ObjectCell<'gc>,
values: Object<'gc>,
}
unsafe impl<'gc> gc_arena::Collect for Scope<'gc> {
@ -43,7 +43,7 @@ unsafe impl<'gc> gc_arena::Collect for Scope<'gc> {
impl<'gc> Scope<'gc> {
/// Construct a global scope (one without a parent).
pub fn from_global_object(globals: ObjectCell<'gc>) -> Scope<'gc> {
pub fn from_global_object(globals: Object<'gc>) -> Scope<'gc> {
Scope {
parent: None,
class: ScopeClass::Global,
@ -119,7 +119,7 @@ impl<'gc> Scope<'gc> {
/// scope has been replaced with another given object.
pub fn new_target_scope(
mut parent: GcCell<'gc, Self>,
clip: ObjectCell<'gc>,
clip: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let mut bottom_scope = None;
@ -176,7 +176,7 @@ impl<'gc> Scope<'gc> {
/// scope. This requires some scope chain juggling.
pub fn new_with_scope(
locals: GcCell<'gc, Self>,
with_object: ObjectCell<'gc>,
with_object: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> GcCell<'gc, Self> {
let parent_scope = locals.read().parent;
@ -204,7 +204,7 @@ impl<'gc> Scope<'gc> {
pub fn new(
parent: GcCell<'gc, Self>,
class: ScopeClass,
with_object: ObjectCell<'gc>,
with_object: Object<'gc>,
) -> Scope<'gc> {
Scope {
parent: Some(parent),
@ -214,18 +214,14 @@ impl<'gc> Scope<'gc> {
}
/// Returns a reference to the current local scope object.
pub fn locals(&self) -> Ref<Box<dyn Object<'gc>>> {
self.values.read()
}
/// Returns a gc cell of the current local scope object.
pub fn locals_cell(&self) -> ObjectCell<'gc> {
self.values.to_owned()
pub fn locals(&self) -> &Object<'gc> {
&self.values
}
/// Returns a reference to the current local scope object for mutation.
pub fn locals_mut(&self, mc: MutationContext<'gc, '_>) -> RefMut<Box<dyn Object<'gc>>> {
self.values.write(mc)
#[allow(dead_code)]
pub fn locals_mut(&mut self) -> &mut Object<'gc> {
&mut self.values
}
/// Returns a reference to the parent scope object.
@ -246,7 +242,7 @@ impl<'gc> Scope<'gc> {
name: &str,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
if self.locals().has_property(name) {
return self.locals().get(name, avm, context, this);
@ -286,11 +282,10 @@ impl<'gc> Scope<'gc> {
value: Value<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<Option<Value<'gc>>, Error> {
if self.locals().has_property(name) && self.locals().is_property_overwritable(name) {
self.locals_mut(context.gc_context)
.set(name, value, avm, context, this)?;
self.locals().set(name, value, avm, context, this)?;
return Ok(None);
}
@ -308,14 +303,14 @@ impl<'gc> Scope<'gc> {
/// chain. As a result, this function always force sets a property on the
/// local object and does not traverse the scope chain.
pub fn define(&self, name: &str, value: impl Into<Value<'gc>>, mc: MutationContext<'gc, '_>) {
self.locals_mut(mc)
.define_value(name, value.into(), EnumSet::empty());
self.locals()
.define_value(mc, name, value.into(), EnumSet::empty());
}
/// Delete a value from scope
pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool {
if self.locals().has_property(name) {
return self.locals_mut(mc).delete(name);
return self.locals().delete(mc, name);
}
if let Some(scope) = self.parent() {

View File

@ -1,11 +1,11 @@
use crate::avm1::function::{Executable, NativeFunction};
use crate::avm1::property::{Attribute, Property};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ObjectCell, UpdateContext, Value};
use crate::avm1::{Avm1, Error, Object, ObjectPtr, TObject, UpdateContext, Value};
use crate::display_object::DisplayNode;
use core::fmt;
use enumset::EnumSet;
use gc_arena::{GcCell, MutationContext};
use gc_arena::{Collect, GcCell, MutationContext};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
@ -13,16 +13,19 @@ pub const TYPE_OF_OBJECT: &str = "object";
pub const TYPE_OF_FUNCTION: &str = "function";
pub const TYPE_OF_MOVIE_CLIP: &str = "movieclip";
#[derive(Clone)]
pub struct ScriptObject<'gc> {
prototype: Option<ObjectCell<'gc>>,
#[derive(Debug, Copy, Clone, Collect)]
#[collect(no_drop)]
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
pub struct ScriptObjectData<'gc> {
prototype: Option<Object<'gc>>,
display_node: Option<DisplayNode<'gc>>,
values: HashMap<String, Property<'gc>>,
function: Option<Executable<'gc>>,
type_of: &'static str,
}
unsafe impl<'gc> gc_arena::Collect for ScriptObject<'gc> {
unsafe impl<'gc> Collect for ScriptObjectData<'gc> {
fn trace(&self, cc: gc_arena::CollectionContext) {
self.prototype.trace(cc);
self.display_node.trace(cc);
@ -31,7 +34,7 @@ unsafe impl<'gc> gc_arena::Collect for ScriptObject<'gc> {
}
}
impl fmt::Debug for ScriptObject<'_> {
impl fmt::Debug for ScriptObjectData<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Object")
.field("prototype", &self.prototype)
@ -44,33 +47,37 @@ impl fmt::Debug for ScriptObject<'_> {
impl<'gc> ScriptObject<'gc> {
pub fn object(
_gc_context: MutationContext<'gc, '_>,
proto: Option<ObjectCell<'gc>>,
) -> ScriptObject<'gc> {
ScriptObject {
prototype: proto,
type_of: TYPE_OF_OBJECT,
display_node: None,
values: HashMap::new(),
function: None,
}
}
/// Constructs and allocates an empty but normal object in one go.
pub fn object_cell(
gc_context: MutationContext<'gc, '_>,
proto: Option<ObjectCell<'gc>>,
) -> ObjectCell<'gc> {
GcCell::allocate(
proto: Option<Object<'gc>>,
) -> ScriptObject<'gc> {
ScriptObject(GcCell::allocate(
gc_context,
Box::new(ScriptObject {
ScriptObjectData {
prototype: proto,
type_of: TYPE_OF_OBJECT,
display_node: None,
values: HashMap::new(),
function: None,
}),
)
},
))
}
/// Constructs and allocates an empty but normal object in one go.
pub fn object_cell(
gc_context: MutationContext<'gc, '_>,
proto: Option<Object<'gc>>,
) -> Object<'gc> {
ScriptObject(GcCell::allocate(
gc_context,
ScriptObjectData {
prototype: proto,
type_of: TYPE_OF_OBJECT,
display_node: None,
values: HashMap::new(),
function: None,
},
))
.into()
}
/// Constructs an object with no values, not even builtins.
@ -78,28 +85,35 @@ impl<'gc> ScriptObject<'gc> {
/// Intended for constructing scope chains, since they exclusively use the
/// object values, but can't just have a hashmap because of `with` and
/// friends.
pub fn bare_object() -> Self {
ScriptObject {
prototype: None,
type_of: TYPE_OF_OBJECT,
display_node: None,
values: HashMap::new(),
function: None,
}
pub fn bare_object(gc_context: MutationContext<'gc, '_>) -> Self {
ScriptObject(GcCell::allocate(
gc_context,
ScriptObjectData {
prototype: None,
type_of: TYPE_OF_OBJECT,
display_node: None,
values: HashMap::new(),
function: None,
},
))
}
/// Construct a function sans prototype.
pub fn bare_function(
gc_context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>,
fn_proto: Option<ObjectCell<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Self {
ScriptObject {
prototype: fn_proto,
type_of: TYPE_OF_FUNCTION,
function: Some(function.into()),
display_node: None,
values: HashMap::new(),
}
ScriptObject(GcCell::allocate(
gc_context,
ScriptObjectData {
prototype: fn_proto,
type_of: TYPE_OF_FUNCTION,
function: Some(function.into()),
display_node: None,
values: HashMap::new(),
},
))
}
/// Construct a function from an executable and associated protos.
@ -113,35 +127,36 @@ impl<'gc> ScriptObject<'gc> {
pub fn function(
gc_context: MutationContext<'gc, '_>,
function: impl Into<Executable<'gc>>,
fn_proto: Option<ObjectCell<'gc>>,
prototype: Option<ObjectCell<'gc>>,
) -> ObjectCell<'gc> {
let function = GcCell::allocate(
gc_context,
Box::new(Self::bare_function(function, fn_proto)) as Box<dyn Object<'gc> + 'gc>,
);
fn_proto: Option<Object<'gc>>,
prototype: Option<Object<'gc>>,
) -> Object<'gc> {
let function = Self::bare_function(gc_context, function, fn_proto).into();
//TODO: Can we make these proper sets or no?
if let Some(p) = prototype {
p.write(gc_context).define_value(
p.define_value(
gc_context,
"constructor",
Value::Object(function),
Attribute::DontEnum.into(),
);
function
.write(gc_context)
.define_value("prototype", p.into(), EnumSet::empty());
function.define_value(gc_context, "prototype", p.into(), EnumSet::empty());
}
function
}
pub fn set_display_node(&mut self, display_node: DisplayNode<'gc>) {
self.display_node = Some(display_node);
pub fn set_display_node(
self,
gc_context: MutationContext<'gc, '_>,
display_node: DisplayNode<'gc>,
) {
self.0.write(gc_context).display_node = Some(display_node);
}
#[allow(dead_code)]
pub fn display_node(&self) -> Option<DisplayNode<'gc>> {
self.display_node
self.0.read().display_node
}
/// Declare a native function on the current object.
@ -157,27 +172,28 @@ impl<'gc> ScriptObject<'gc> {
function: NativeFunction<'gc>,
gc_context: MutationContext<'gc, '_>,
attributes: A,
fn_proto: Option<ObjectCell<'gc>>,
fn_proto: Option<Object<'gc>>,
) where
A: Into<EnumSet<Attribute>>,
{
self.define_value(
gc_context,
name,
Value::Object(ScriptObject::function(gc_context, function, fn_proto, None)),
attributes.into(),
)
}
pub fn set_prototype(&mut self, prototype: ObjectCell<'gc>) {
self.prototype = Some(prototype);
pub fn set_prototype(&mut self, gc_context: MutationContext<'gc, '_>, prototype: Object<'gc>) {
self.0.write(gc_context).prototype = Some(prototype);
}
pub fn set_type_of(&mut self, type_of: &'static str) {
self.type_of = type_of;
pub fn set_type_of(&mut self, gc_context: MutationContext<'gc, '_>, type_of: &'static str) {
self.0.write(gc_context).type_of = type_of;
}
}
impl<'gc> Object<'gc> for ScriptObject<'gc> {
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
/// Get the value of a particular property on this object.
///
/// The `avm`, `context`, and `this` parameters exist so that this object
@ -191,16 +207,13 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
name: &str,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
if name == "__proto__" {
return Ok(self
.prototype
.map_or(Value::Undefined, Value::Object)
.into());
return Ok(self.proto().map_or(Value::Undefined, Value::Object).into());
}
if let Some(value) = self.values.get(name) {
if let Some(value) = self.0.read().values.get(name) {
return value.get(avm, context, this);
}
@ -213,17 +226,22 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
/// the object's own `GcCell`, so that it can pass it to user-defined
/// overrides that may need to interact with the underlying object.
fn set(
&mut self,
&self,
name: &str,
value: Value<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
) -> Result<(), Error> {
if name == "__proto__" {
self.prototype = value.as_object().ok().to_owned();
self.0.write(context.gc_context).prototype = value.as_object().ok();
} else {
match self.values.entry(name.to_owned()) {
match self
.0
.write(context.gc_context)
.values
.entry(name.to_owned())
{
Entry::Occupied(mut entry) => {
entry.get_mut().set(avm, context, this, value)?;
}
@ -248,10 +266,10 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(function) = &self.function {
if let Some(function) = &self.0.read().function {
function.exec(avm, context, this, args)
} else {
Ok(Value::Undefined.into())
@ -263,22 +281,20 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
&self,
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ObjectCell<'gc>, Error> {
Ok(GcCell::allocate(
context.gc_context,
Box::new(ScriptObject::object(context.gc_context, Some(this))) as Box<dyn Object<'gc>>,
))
) -> Result<Object<'gc>, Error> {
Ok(ScriptObject::object(context.gc_context, Some(this)).into())
}
/// Delete a named property from the object.
///
/// Returns false if the property cannot be deleted.
fn delete(&mut self, name: &str) -> bool {
if let Some(prop) = self.values.get(name) {
fn delete(&self, gc_context: MutationContext<'gc, '_>, name: &str) -> bool {
let mut object = self.0.write(gc_context);
if let Some(prop) = object.values.get(name) {
if prop.can_delete() {
self.values.remove(name);
object.values.remove(name);
return true;
}
}
@ -287,13 +303,14 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
}
fn add_property(
&mut self,
&self,
gc_context: MutationContext<'gc, '_>,
name: &str,
get: Executable<'gc>,
set: Option<Executable<'gc>>,
attributes: EnumSet<Attribute>,
) {
self.values.insert(
self.0.write(gc_context).values.insert(
name.to_owned(),
Property::Virtual {
get,
@ -303,22 +320,30 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
);
}
fn define_value(&mut self, name: &str, value: Value<'gc>, attributes: EnumSet<Attribute>) {
self.values
fn define_value(
&self,
gc_context: MutationContext<'gc, '_>,
name: &str,
value: Value<'gc>,
attributes: EnumSet<Attribute>,
) {
self.0
.write(gc_context)
.values
.insert(name.to_string(), Property::Stored { value, attributes });
}
fn proto(&self) -> Option<ObjectCell<'gc>> {
self.prototype
fn proto(&self) -> Option<Object<'gc>> {
self.0.read().prototype
}
/// Checks if the object has a given named property.
fn has_property(&self, name: &str) -> bool {
self.has_own_property(name)
|| self
.prototype
.proto()
.as_ref()
.map_or(false, |p| p.read().has_property(name))
.map_or(false, |p| p.has_property(name))
}
/// Checks if the object has a given named property on itself (and not,
@ -327,11 +352,13 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
if name == "__proto__" {
return true;
}
self.values.contains_key(name)
self.0.read().values.contains_key(name)
}
fn is_property_overwritable(&self, name: &str) -> bool {
self.values
self.0
.read()
.values
.get(name)
.map(|p| p.is_overwritable())
.unwrap_or(false)
@ -339,7 +366,7 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
/// Checks if a named property appears when enumerating the object.
fn is_property_enumerable(&self, name: &str) -> bool {
if let Some(prop) = self.values.get(name) {
if let Some(prop) = self.0.read().values.get(name) {
prop.is_enumerable()
} else {
false
@ -348,11 +375,11 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
/// Enumerate the object.
fn get_keys(&self) -> HashSet<String> {
let mut result = self
.prototype
.map_or_else(HashSet::new, |p| p.read().get_keys());
let mut result = self.proto().map_or_else(HashSet::new, |p| p.get_keys());
self.values
self.0
.read()
.values
.iter()
.filter_map(|(k, p)| {
if p.is_enumerable() {
@ -369,7 +396,7 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
}
fn as_string(&self) -> String {
if self.function.is_some() {
if self.0.read().function.is_some() {
"[type Function]".to_string()
} else {
"[object Object]".to_string()
@ -377,7 +404,7 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
}
fn type_of(&self) -> &'static str {
self.type_of
self.0.read().type_of
}
fn as_script_object(&self) -> Option<&ScriptObject<'gc>> {
@ -390,7 +417,7 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
/// Get the underlying display node for this object, if it exists.
fn as_display_node(&self) -> Option<DisplayNode<'gc>> {
self.display_node
self.0.read().display_node
}
/// Returns a copy of a given function.
@ -398,7 +425,11 @@ impl<'gc> Object<'gc> for ScriptObject<'gc> {
/// TODO: We have to clone here because of how executables are stored on
/// objects directly. This might not be a good idea for performance.
fn as_executable(&self) -> Option<Executable<'gc>> {
self.function.clone()
self.0.read().function.clone()
}
fn as_ptr(&self) -> *const ObjectPtr {
self.0.as_ptr() as *const ObjectPtr
}
}
@ -411,7 +442,7 @@ mod tests {
use crate::backend::audio::NullAudioBackend;
use crate::backend::navigator::NullNavigatorBackend;
use crate::backend::render::NullRenderer;
use crate::display_object::{DisplayObject, MovieClip};
use crate::display_object::MovieClip;
use crate::library::Library;
use crate::prelude::*;
use gc_arena::rootless_arena;
@ -420,11 +451,7 @@ mod tests {
fn with_object<F, R>(swf_version: u8, test: F) -> R
where
F: for<'a, 'gc> FnOnce(
&mut Avm1<'gc>,
&mut UpdateContext<'a, 'gc, '_>,
ObjectCell<'gc>,
) -> R,
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>) -> R,
{
rootless_arena(|gc_context| {
let mut avm = Avm1::new(gc_context, swf_version);
@ -457,13 +484,7 @@ mod tests {
system_prototypes: avm.prototypes().clone(),
};
let object = GcCell::allocate(
gc_context,
Box::new(ScriptObject::object(
gc_context,
Some(avm.prototypes().object),
)) as Box<dyn Object<'_>>,
);
let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into();
let globals = avm.global_object_cell();
avm.insert_stack_frame(GcCell::allocate(
@ -479,10 +500,7 @@ mod tests {
fn test_get_undefined() {
with_object(0, |avm, context, object| {
assert_eq!(
object
.read()
.get("not_defined", avm, context, object)
.unwrap(),
object.get("not_defined", avm, context, object).unwrap(),
ReturnValue::Immediate(Value::Undefined)
);
})
@ -490,23 +508,23 @@ mod tests {
#[test]
fn test_set_get() {
with_object(0, |avm, context, object| {
with_object(0, |avm, context, mut object| {
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"forced",
"forced".into(),
EnumSet::empty(),
);
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("forced", "forced".into(), EnumSet::empty());
object
.write(context.gc_context)
.set("natural", "natural".into(), avm, context, object)
.unwrap();
assert_eq!(
object.read().get("forced", avm, context, object).unwrap(),
object.get("forced", avm, context, object).unwrap(),
ReturnValue::Immediate("forced".into())
);
assert_eq!(
object.read().get("natural", avm, context, object).unwrap(),
object.get("natural", avm, context, object).unwrap(),
ReturnValue::Immediate("natural".into())
);
})
@ -514,33 +532,33 @@ mod tests {
#[test]
fn test_set_readonly() {
with_object(0, |avm, context, object| {
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("normal", "initial".into(), EnumSet::empty());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("readonly", "initial".into(), ReadOnly.into());
with_object(0, |avm, context, mut object| {
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"normal",
"initial".into(),
EnumSet::empty(),
);
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"readonly",
"initial".into(),
ReadOnly.into(),
);
object
.write(context.gc_context)
.set("normal", "replaced".into(), avm, context, object)
.unwrap();
object
.write(context.gc_context)
.set("readonly", "replaced".into(), avm, context, object)
.unwrap();
assert_eq!(
object.read().get("normal", avm, context, object).unwrap(),
object.get("normal", avm, context, object).unwrap(),
ReturnValue::Immediate("replaced".into())
);
assert_eq!(
object.read().get("readonly", avm, context, object).unwrap(),
object.get("readonly", avm, context, object).unwrap(),
ReturnValue::Immediate("initial".into())
);
})
@ -548,29 +566,30 @@ mod tests {
#[test]
fn test_deletable_not_readonly() {
with_object(0, |avm, context, object| {
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("test", "initial".into(), DontDelete.into());
with_object(0, |avm, context, mut object| {
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"test",
"initial".into(),
DontDelete.into(),
);
assert_eq!(object.write(context.gc_context).delete("test"), false);
assert_eq!(object.delete(context.gc_context, "test"), false);
assert_eq!(
object.read().get("test", avm, context, object).unwrap(),
object.get("test", avm, context, object).unwrap(),
ReturnValue::Immediate("initial".into())
);
let this = object;
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.set("test", "replaced".into(), avm, context, object)
.set("test", "replaced".into(), avm, context, this)
.unwrap();
assert_eq!(object.write(context.gc_context).delete("test"), false);
assert_eq!(object.delete(context.gc_context, "test"), false);
assert_eq!(
object.read().get("test", avm, context, object).unwrap(),
object.get("test", avm, context, object).unwrap(),
ReturnValue::Immediate("replaced".into())
);
})
@ -578,29 +597,30 @@ mod tests {
#[test]
fn test_virtual_get() {
with_object(0, |avm, context, object| {
with_object(0, |avm, context, mut object| {
let getter = Executable::Native(|_avm, _context, _this, _args| {
Ok(ReturnValue::Immediate("Virtual!".into()))
});
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.add_property("test", getter, None, EnumSet::empty());
object.as_script_object_mut().unwrap().add_property(
context.gc_context,
"test",
getter,
None,
EnumSet::empty(),
);
assert_eq!(
object.read().get("test", avm, context, object).unwrap(),
object.get("test", avm, context, object).unwrap(),
ReturnValue::Immediate("Virtual!".into())
);
// This set should do nothing
object
.write(context.gc_context)
.set("test", "Ignored!".into(), avm, context, object)
.unwrap();
assert_eq!(
object.read().get("test", avm, context, object).unwrap(),
object.get("test", avm, context, object).unwrap(),
ReturnValue::Immediate("Virtual!".into())
);
})
@ -608,61 +628,58 @@ mod tests {
#[test]
fn test_delete() {
with_object(0, |avm, context, object| {
with_object(0, |avm, context, mut object| {
let getter = Executable::Native(|_avm, _context, _this, _args| {
Ok(ReturnValue::Immediate("Virtual!".into()))
});
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.add_property("virtual", getter.clone(), None, EnumSet::empty());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.add_property("virtual_un", getter, None, DontDelete.into());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("stored", "Stored!".into(), EnumSet::empty());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("stored_un", "Stored!".into(), DontDelete.into());
assert_eq!(object.write(context.gc_context).delete("virtual"), true);
assert_eq!(object.write(context.gc_context).delete("virtual_un"), false);
assert_eq!(object.write(context.gc_context).delete("stored"), true);
assert_eq!(object.write(context.gc_context).delete("stored_un"), false);
assert_eq!(
object.write(context.gc_context).delete("non_existent"),
false
object.as_script_object_mut().unwrap().add_property(
context.gc_context,
"virtual",
getter.clone(),
None,
EnumSet::empty(),
);
object.as_script_object_mut().unwrap().add_property(
context.gc_context,
"virtual_un",
getter,
None,
DontDelete.into(),
);
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"stored",
"Stored!".into(),
EnumSet::empty(),
);
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"stored_un",
"Stored!".into(),
DontDelete.into(),
);
assert_eq!(object.delete(context.gc_context, "virtual"), true);
assert_eq!(object.delete(context.gc_context, "virtual_un"), false);
assert_eq!(object.delete(context.gc_context, "stored"), true);
assert_eq!(object.delete(context.gc_context, "stored_un"), false);
assert_eq!(object.delete(context.gc_context, "non_existent"), false);
assert_eq!(
object.read().get("virtual", avm, context, object).unwrap(),
object.get("virtual", avm, context, object).unwrap(),
ReturnValue::Immediate(Value::Undefined)
);
assert_eq!(
object
.read()
.get("virtual_un", avm, context, object)
.unwrap(),
object.get("virtual_un", avm, context, object).unwrap(),
ReturnValue::Immediate("Virtual!".into())
);
assert_eq!(
object.read().get("stored", avm, context, object).unwrap(),
object.get("stored", avm, context, object).unwrap(),
ReturnValue::Immediate(Value::Undefined)
);
assert_eq!(
object
.read()
.get("stored_un", avm, context, object)
.unwrap(),
object.get("stored_un", avm, context, object).unwrap(),
ReturnValue::Immediate("Stored!".into())
);
})
@ -670,33 +687,39 @@ mod tests {
#[test]
fn test_iter_values() {
with_object(0, |_avm, context, object| {
with_object(0, |_avm, context, mut object| {
let getter = Executable::Native(|_avm, _context, _this, _args| {
Ok(ReturnValue::Immediate(Value::Null))
});
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("stored", Value::Null, EnumSet::empty());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.define_value("stored_hidden", Value::Null, DontEnum.into());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.add_property("virtual", getter.clone(), None, EnumSet::empty());
object
.write(context.gc_context)
.as_script_object_mut()
.unwrap()
.add_property("virtual_hidden", getter, None, DontEnum.into());
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"stored",
Value::Null,
EnumSet::empty(),
);
object.as_script_object_mut().unwrap().define_value(
context.gc_context,
"stored_hidden",
Value::Null,
DontEnum.into(),
);
object.as_script_object_mut().unwrap().add_property(
context.gc_context,
"virtual",
getter.clone(),
None,
EnumSet::empty(),
);
object.as_script_object_mut().unwrap().add_property(
context.gc_context,
"virtual_hidden",
getter,
None,
DontEnum.into(),
);
let keys = object.read().get_keys();
let keys = object.get_keys();
assert_eq!(keys.len(), 2);
assert_eq!(keys.contains(&"stored".to_string()), true);
assert_eq!(keys.contains(&"stored_hidden".to_string()), false);

View File

@ -1,5 +1,5 @@
use crate::avm1::activation::Activation;
use crate::avm1::{Avm1, ObjectCell, UpdateContext, Value};
use crate::avm1::{Avm1, Object, UpdateContext, Value};
use crate::backend::audio::NullAudioBackend;
use crate::backend::navigator::NullNavigatorBackend;
use crate::backend::render::NullRenderer;
@ -13,11 +13,11 @@ use std::sync::Arc;
pub fn with_avm<F, R>(swf_version: u8, test: F) -> R
where
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, ObjectCell<'gc>) -> R,
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>) -> R,
{
fn in_the_arena<'gc, F, R>(swf_version: u8, test: F, gc_context: MutationContext<'gc, '_>) -> R
where
F: for<'a> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, ObjectCell<'gc>) -> R,
F: for<'a> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>, Object<'gc>) -> R,
{
let mut avm = Avm1::new(gc_context, swf_version);
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(swf_version, gc_context));
@ -54,7 +54,7 @@ where
Activation::from_nothing(swf_version, globals, gc_context),
));
let this = root.read().object().as_object().unwrap().to_owned();
let this = root.read().object().as_object().unwrap();
test(&mut avm, &mut context, this)
}
@ -71,7 +71,7 @@ macro_rules! test_method {
for version in &$versions {
let _ = with_avm(*version, |avm, context, _root| -> Result<(), Error> {
let object = $object(avm, context);
let function = object.read().get($name, avm, context, object)?.unwrap_immediate();
let function = object.get($name, avm, context, object)?.unwrap_immediate();
$(
#[allow(unused_mut)]

View File

@ -1,5 +1,6 @@
use crate::avm1::activation::Activation;
use crate::avm1::test_utils::with_avm;
use crate::avm1::TObject;
use gc_arena::GcCell;
#[test]
@ -7,14 +8,12 @@ fn locals_into_form_values() {
with_avm(19, |avm, context, _this| {
let my_activation =
Activation::from_nothing(19, avm.global_object_cell(), context.gc_context);
let my_locals = my_activation.scope().locals_cell();
let my_locals = my_activation.scope().locals().to_owned();
my_locals
.write(context.gc_context)
.set("value1", "string".into(), avm, context, my_locals)
.unwrap();
my_locals
.write(context.gc_context)
.set("value2", 2.0.into(), avm, context, my_locals)
.unwrap();

View File

@ -1,9 +1,8 @@
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, ObjectCell, UpdateContext};
use gc_arena::GcCell;
use crate::avm1::{Avm1, Error, Object, TObject, UpdateContext};
use std::f64::NAN;
#[derive(Clone, Debug)]
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum Value<'gc> {
Undefined,
@ -11,7 +10,7 @@ pub enum Value<'gc> {
Bool(bool),
Number(f64),
String(String),
Object(ObjectCell<'gc>),
Object(Object<'gc>),
}
impl<'gc> From<String> for Value<'gc> {
@ -32,8 +31,8 @@ impl<'gc> From<bool> for Value<'gc> {
}
}
impl<'gc> From<ObjectCell<'gc>> for Value<'gc> {
fn from(object: ObjectCell<'gc>) -> Self {
impl<'gc> From<Object<'gc>> for Value<'gc> {
fn from(object: Object<'gc>) -> Self {
Value::Object(object)
}
}
@ -108,7 +107,7 @@ impl PartialEq for Value<'_> {
_ => false,
},
Value::Object(value) => match other {
Value::Object(other_value) => value.as_ptr() == other_value.as_ptr(),
Value::Object(other_value) => Object::ptr_eq(*value, *other_value),
_ => false,
},
}
@ -213,7 +212,6 @@ impl<'gc> Value<'gc> {
Ok(match self {
Value::Object(object) => {
let value_of_impl = object
.read()
.get("valueOf", avm, context, *object)?
.resolve(avm, context)?;
@ -294,12 +292,12 @@ impl<'gc> Value<'gc> {
}
(Value::String(a), Value::String(b)) => Ok((a == b).into()),
(Value::Bool(a), Value::Bool(b)) => Ok((a == b).into()),
(Value::Object(a), Value::Object(b)) => Ok(GcCell::ptr_eq(*a, *b).into()),
(Value::Object(a), Value::Object(b)) => Ok(Object::ptr_eq(*a, *b).into()),
(Value::Object(a), Value::Null) | (Value::Object(a), Value::Undefined) => {
Ok((a.as_ptr() == avm.global_object_cell().as_ptr()).into())
Ok(Object::ptr_eq(*a, avm.global_object_cell()).into())
}
(Value::Null, Value::Object(b)) | (Value::Undefined, Value::Object(b)) => {
Ok((b.as_ptr() == avm.global_object_cell().as_ptr()).into())
Ok(Object::ptr_eq(*b, avm.global_object_cell()).into())
}
(Value::Undefined, Value::Null) => Ok(true.into()),
(Value::Null, Value::Undefined) => Ok(true.into()),
@ -376,7 +374,7 @@ impl<'gc> Value<'gc> {
Value::Bool(v) => v.to_string(),
Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int?
Value::String(v) => v,
Value::Object(object) => object.read().as_string(),
Value::Object(object) => object.as_string(),
}
}
@ -389,7 +387,6 @@ impl<'gc> Value<'gc> {
Ok(match self {
Value::Object(object) => {
let to_string_impl = object
.read()
.get("toString", avm, context, object)?
.resolve(avm, context)?;
let fake_args = Vec::new();
@ -430,7 +427,7 @@ impl<'gc> Value<'gc> {
Value::Number(_) => "number",
Value::Bool(_) => "boolean",
Value::String(_) => "string",
Value::Object(object) => object.read().type_of(),
Value::Object(object) => object.type_of(),
}
.to_string(),
)
@ -448,6 +445,7 @@ impl<'gc> Value<'gc> {
self.as_f64().map(|n| n as i64)
}
#[allow(dead_code)]
pub fn as_usize(&self) -> Result<usize, Error> {
self.as_f64().map(|n| n as usize)
}
@ -466,9 +464,9 @@ impl<'gc> Value<'gc> {
}
}
pub fn as_object(&self) -> Result<ObjectCell<'gc>, Error> {
pub fn as_object(&self) -> Result<Object<'gc>, Error> {
if let Value::Object(object) = self {
Ok(object.to_owned())
Ok(*object)
} else {
Err(format!("Expected Object, found {:?}", self).into())
}
@ -478,11 +476,11 @@ impl<'gc> Value<'gc> {
&self,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: ObjectCell<'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Value::Object(object) = self {
object.read().call(avm, context, this, args)
object.call(avm, context, this, args)
} else {
Ok(Value::Undefined.into())
}
@ -493,14 +491,13 @@ impl<'gc> Value<'gc> {
mod test {
use crate::avm1::function::Executable;
use crate::avm1::globals::create_globals;
use crate::avm1::object::ObjectCell;
use crate::avm1::object::{Object, TObject};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::script_object::ScriptObject;
use crate::avm1::test_utils::with_avm;
use crate::avm1::{Avm1, Error, Value};
use crate::context::UpdateContext;
use enumset::EnumSet;
use gc_arena::GcCell;
use std::f64::{INFINITY, NAN, NEG_INFINITY};
#[test]
@ -524,7 +521,7 @@ mod test {
fn value_of_impl<'gc>(
_: &mut Avm1<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: ObjectCell<'gc>,
_: Object<'gc>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(5.0.into())
@ -538,8 +535,12 @@ mod test {
);
let o = ScriptObject::object_cell(context.gc_context, Some(protos.object));
o.write(context.gc_context)
.define_value("valueOf", valueof.into(), EnumSet::empty());
o.define_value(
context.gc_context,
"valueOf",
valueof.into(),
EnumSet::empty(),
);
assert_eq!(
Value::Object(o).to_primitive_num(avm, context).unwrap(),
@ -562,10 +563,7 @@ mod test {
assert_eq!(f.as_number(avm, context).unwrap(), 0.0);
assert!(n.as_number(avm, context).unwrap().is_nan());
let bo = Value::Object(GcCell::allocate(
context.gc_context,
Box::new(ScriptObject::bare_object()),
));
let bo = Value::Object(ScriptObject::bare_object(context.gc_context).into());
assert!(bo.as_number(avm, context).unwrap().is_nan());
});
@ -585,10 +583,7 @@ mod test {
assert_eq!(f.as_number(avm, context).unwrap(), 0.0);
assert_eq!(n.as_number(avm, context).unwrap(), 0.0);
let bo = Value::Object(GcCell::allocate(
context.gc_context,
Box::new(ScriptObject::bare_object()),
));
let bo = Value::Object(ScriptObject::bare_object(context.gc_context).into());
assert_eq!(bo.as_number(avm, context).unwrap(), 0.0);
});

View File

@ -1,4 +1,4 @@
use crate::avm1::{ObjectCell, Value};
use crate::avm1::{Object, Value};
use crate::context::{RenderContext, UpdateContext};
use crate::player::NEWEST_PLAYER_VERSION;
use crate::prelude::*;
@ -259,7 +259,7 @@ pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
&mut self,
_gc_context: MutationContext<'gc, '_>,
_display_object: DisplayNode<'gc>,
_proto: ObjectCell<'gc>,
_proto: Object<'gc>,
) {
}

View File

@ -1,6 +1,6 @@
//! `MovieClip` display object and support code.
use crate::avm1::script_object::TYPE_OF_MOVIE_CLIP;
use crate::avm1::{ObjectCell, ScriptObject, Value};
use crate::avm1::{Object, ScriptObject, TObject, Value};
use crate::backend::audio::AudioStreamHandle;
use crate::character::Character;
use crate::context::{RenderContext, UpdateContext};
@ -33,7 +33,7 @@ pub struct MovieClip<'gc> {
current_frame: FrameNumber,
audio_stream: Option<AudioStreamHandle>,
children: BTreeMap<Depth, DisplayNode<'gc>>,
object: ObjectCell<'gc>,
object: Object<'gc>,
}
impl<'gc> MovieClip<'gc> {
@ -47,7 +47,7 @@ impl<'gc> MovieClip<'gc> {
current_frame: 0,
audio_stream: None,
children: BTreeMap::new(),
object: GcCell::allocate(gc_context, Box::new(ScriptObject::bare_object())),
object: ScriptObject::bare_object(gc_context).into(),
}
}
@ -78,7 +78,7 @@ impl<'gc> MovieClip<'gc> {
current_frame: 0,
audio_stream: None,
children: BTreeMap::new(),
object: GcCell::allocate(gc_context, Box::new(ScriptObject::bare_object())),
object: ScriptObject::bare_object(gc_context).into(),
}
}
@ -626,18 +626,12 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
&mut self,
gc_context: MutationContext<'gc, '_>,
display_object: DisplayNode<'gc>,
proto: ObjectCell<'gc>,
proto: Object<'gc>,
) {
let mut object = self.object.write(gc_context);
object
.as_script_object_mut()
.unwrap()
.set_display_node(display_object);
object
.as_script_object_mut()
.unwrap()
.set_type_of(TYPE_OF_MOVIE_CLIP);
object.as_script_object_mut().unwrap().set_prototype(proto);
let object = self.object.as_script_object_mut().unwrap();
object.set_display_node(gc_context, display_object);
object.set_type_of(gc_context, TYPE_OF_MOVIE_CLIP);
object.set_prototype(gc_context, proto);
}
fn object(&self) -> Value<'gc> {

View File

@ -1,5 +1,5 @@
use crate::avm1::globals::SystemPrototypes;
use crate::avm1::ObjectCell;
use crate::avm1::Object;
use crate::backend::audio::SoundHandle;
use crate::character::Character;
use crate::display_object::DisplayObject;
@ -51,7 +51,7 @@ impl<'gc> Library<'gc> {
gc_context: MutationContext<'gc, '_>,
prototypes: &SystemPrototypes<'gc>,
) -> Result<DisplayNode<'gc>, Box<dyn std::error::Error>> {
let (obj, proto): (Box<dyn DisplayObject<'gc>>, ObjectCell<'gc>) = match self
let (obj, proto): (Box<dyn DisplayObject<'gc>>, Object<'gc>) = match self
.characters
.get(&id)
{

View File

@ -1973,12 +1973,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -1993,17 +1995,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2120,7 +2125,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2132,6 +2138,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2146,6 +2153,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2153,12 +2161,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2177,6 +2187,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2257,7 +2268,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2269,6 +2281,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2390,6 +2403,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",