From 23ca66a7e35d3fe70cca9441673fb560780993b0 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Fri, 6 Dec 2019 10:24:36 -0800 Subject: [PATCH] avm1: Use enum_trait_object for avm1::Object --- core/Cargo.toml | 1 + core/src/avm1.rs | 115 +++---- core/src/avm1/activation.rs | 24 +- core/src/avm1/function.rs | 25 +- core/src/avm1/globals.rs | 56 +-- core/src/avm1/globals/function.rs | 35 +- core/src/avm1/globals/math.rs | 61 ++-- core/src/avm1/globals/movie_clip.rs | 34 +- core/src/avm1/globals/object.rs | 62 ++-- core/src/avm1/object.rs | 59 +++- core/src/avm1/property.rs | 6 +- core/src/avm1/return_value.rs | 7 +- core/src/avm1/scope.rs | 41 +-- core/src/avm1/script_object.rs | 475 ++++++++++++++------------ core/src/avm1/test_utils.rs | 10 +- core/src/avm1/tests.rs | 5 +- core/src/avm1/value.rs | 57 ++-- core/src/display_object.rs | 4 +- core/src/display_object/movie_clip.rs | 24 +- core/src/library.rs | 4 +- web/selfhosted/package-lock.json | 28 +- 21 files changed, 561 insertions(+), 572 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 383f49a98..6487c98c2 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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" diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 9dc18a966..3d2f00550 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -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, /// 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 { 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>, - ); + 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>, - ); + 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() diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index e9e917698..e5026703c 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -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>, + arguments: Option>, /// The return value of the activation. return_value: Option>, @@ -114,8 +114,8 @@ impl<'gc> Activation<'gc> { swf_version: u8, code: SwfSlice, scope: GcCell<'gc, Scope<'gc>>, - this: ObjectCell<'gc>, - arguments: Option>, + this: Object<'gc>, + arguments: Option>, ) -> 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>, + this: Object<'gc>, + arguments: Option>, ) -> 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> { 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> { self.return_value.clone() } diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 5fcd3771f..891833e43 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -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, 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, 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 + '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) }; diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index be0aee65f..cf8e725c0 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -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, 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, 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, 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, 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, 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, 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, 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 + '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 } diff --git a/core/src/avm1/globals/function.rs b/core/src/avm1/globals/function.rs index 344af39a1..0241066e6 100644 --- a/core/src/avm1/globals/function.rs +++ b/core/src/avm1/globals/function.rs @@ -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, 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, 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, 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 } diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 2086ce54c..a8b3940f7 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -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, 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, 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>, - fn_proto: Option>, -) -> ObjectCell<'gc> { + proto: Option>, + fn_proto: Option>, +) -> 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() ); }); diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 60e7ca471..e34fd0f56 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -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, Error> { Ok(Value::Undefined.into()) @@ -24,7 +24,7 @@ macro_rules! with_movie_clip { $object.force_set_function( $name, |_avm, _context, this, args| -> Result, 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, 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, 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, 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() } diff --git a/core/src/avm1/globals/object.rs b/core/src/avm1/globals/object.rs index 1610f3409..8d970a185 100644 --- a/core/src/avm1/globals/object.rs +++ b/core/src/avm1/globals/object.rs @@ -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, 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, 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, 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, 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, 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, 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, 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( diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index f2565fad3..5644c4cc6 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -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 + '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, 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, 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, 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, Error>; + ) -> Result, 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>; + fn proto(&self) -> Option>; /// 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); + fn define_value( + &self, + gc_context: MutationContext<'gc, '_>, + name: &str, + value: Value<'gc>, + attributes: EnumSet, + ); /// 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>, @@ -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>; + + 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() + } } diff --git a/core/src/avm1/property.rs b/core/src/avm1/property.rs index 7839765da..a1a74a158 100644 --- a/core/src/avm1/property.rs +++ b/core/src/avm1/property.rs @@ -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, 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>, ) -> Result { match self { diff --git a/core/src/avm1/return_value.rs b/core/src/avm1/return_value.rs index 8ad48666e..934001130 100644 --- a/core/src/avm1/return_value.rs +++ b/core/src/avm1/return_value.rs @@ -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 for ReturnValue<'gc> { } } -impl<'gc> From> for ReturnValue<'gc> { - fn from(object: ObjectCell<'gc>) -> Self { +impl<'gc> From> for ReturnValue<'gc> { + fn from(object: Object<'gc>) -> Self { ReturnValue::Immediate(Value::Object(object)) } } diff --git a/core/src/avm1/scope.rs b/core/src/avm1/scope.rs index 406a69041..9a01f5894 100644 --- a/core/src/avm1/scope.rs +++ b/core/src/avm1/scope.rs @@ -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>>, 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>> { - 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>> { - 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, 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>, 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>, 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() { diff --git a/core/src/avm1/script_object.rs b/core/src/avm1/script_object.rs index dd7691974..9f45d7a89 100644 --- a/core/src/avm1/script_object.rs +++ b/core/src/avm1/script_object.rs @@ -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>, +#[derive(Debug, Copy, Clone, Collect)] +#[collect(no_drop)] +pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); + +pub struct ScriptObjectData<'gc> { + prototype: Option>, display_node: Option>, values: HashMap>, function: Option>, 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>, - ) -> 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> { - GcCell::allocate( + proto: Option>, + ) -> 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> { + 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>, - fn_proto: Option>, + fn_proto: Option>, ) -> 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>, - fn_proto: Option>, - prototype: Option>, - ) -> ObjectCell<'gc> { - let function = GcCell::allocate( - gc_context, - Box::new(Self::bare_function(function, fn_proto)) as Box + 'gc>, - ); + fn_proto: Option>, + prototype: Option>, + ) -> 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> { - 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>, + fn_proto: Option>, ) where A: Into>, { 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, 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, 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, Error> { - Ok(GcCell::allocate( - context.gc_context, - Box::new(ScriptObject::object(context.gc_context, Some(this))) as Box>, - )) + ) -> Result, 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>, attributes: EnumSet, ) { - 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) { - self.values + fn define_value( + &self, + gc_context: MutationContext<'gc, '_>, + name: &str, + value: Value<'gc>, + attributes: EnumSet, + ) { + self.0 + .write(gc_context) + .values .insert(name.to_string(), Property::Stored { value, attributes }); } - fn proto(&self) -> Option> { - self.prototype + fn proto(&self) -> Option> { + 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 { - 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> { - 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> { - 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(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>, - ); + 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); diff --git a/core/src/avm1/test_utils.rs b/core/src/avm1/test_utils.rs index 59f15a2e9..55c57253a 100644 --- a/core/src/avm1/test_utils.rs +++ b/core/src/avm1/test_utils.rs @@ -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(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 = 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)] diff --git a/core/src/avm1/tests.rs b/core/src/avm1/tests.rs index 725a1a4eb..25754cc6c 100644 --- a/core/src/avm1/tests.rs +++ b/core/src/avm1/tests.rs @@ -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(); diff --git a/core/src/avm1/value.rs b/core/src/avm1/value.rs index e09d6e3f4..ebf2f5073 100644 --- a/core/src/avm1/value.rs +++ b/core/src/avm1/value.rs @@ -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 for Value<'gc> { @@ -32,8 +31,8 @@ impl<'gc> From for Value<'gc> { } } -impl<'gc> From> for Value<'gc> { - fn from(object: ObjectCell<'gc>) -> Self { +impl<'gc> From> 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 { self.as_f64().map(|n| n as usize) } @@ -466,9 +464,9 @@ impl<'gc> Value<'gc> { } } - pub fn as_object(&self) -> Result, Error> { + pub fn as_object(&self) -> Result, 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, 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, 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); }); diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 6d623906b..6ea7eebe7 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -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>, ) { } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 6c6ec18bb..f877f6fe0 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -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, children: BTreeMap>, - 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> { diff --git a/core/src/library.rs b/core/src/library.rs index af34ba94d..d0402c6a6 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -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, Box> { - let (obj, proto): (Box>, ObjectCell<'gc>) = match self + let (obj, proto): (Box>, Object<'gc>) = match self .characters .get(&id) { diff --git a/web/selfhosted/package-lock.json b/web/selfhosted/package-lock.json index 97b326532..530a27305 100644 --- a/web/selfhosted/package-lock.json +++ b/web/selfhosted/package-lock.json @@ -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",