diff --git a/core/Cargo.toml b/core/Cargo.toml index 1a9a415f5..26edd6055 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -16,6 +16,7 @@ minimp3 = { version = "0.3.3", optional = true } puremp3 = { version = "0.1", optional = true } rand = "0.6.5" swf = { path = "../swf" } +enumset = "0.4.2" [dependencies.jpeg-decoder] version = "0.1.16" diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 0ebe0e9b2..e42aa4151 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -822,11 +822,8 @@ impl<'gc> Avm1<'gc> { let name = name_val.as_string()?; let object = self.pop()?.as_object()?; - //Fun fact: This isn't in the Adobe SWF19 spec, but this opcode returns - //a boolean based on if the delete actually deleted something. - let did_exist = Value::Bool(object.read().has_property(name)); - object.write(context.gc_context).delete(name); - self.push(did_exist); + let success = object.write(context.gc_context).delete(name); + self.push(Value::Bool(success)); Ok(()) } diff --git a/core/src/avm1/function.rs b/core/src/avm1/function.rs index 0336c3710..1028ef3be 100644 --- a/core/src/avm1/function.rs +++ b/core/src/avm1/function.rs @@ -1,7 +1,7 @@ //! Code relating to executable functions + calling conventions. use crate::avm1::activation::Activation; -use crate::avm1::object::Object; +use crate::avm1::object::{Attribute, Object}; use crate::avm1::scope::Scope; use crate::avm1::value::Value; use crate::avm1::{ActionContext, Avm1}; @@ -201,10 +201,18 @@ impl<'gc> Executable<'gc> { let mut arguments = Object::object(ac.gc_context); for i in 0..args.len() { - arguments.force_set(&format!("{}", i), args.get(i).unwrap().clone()); + arguments.force_set( + &format!("{}", i), + args.get(i).unwrap().clone(), + Attribute::DontDelete, + ); } - arguments.force_set("length", Value::Number(args.len() as f64)); + arguments.force_set( + "length", + Value::Number(args.len() as f64), + Attribute::DontDelete, + ); let argcell = GcCell::allocate(ac.gc_context, arguments); let child_scope = GcCell::allocate( ac.gc_context, @@ -240,10 +248,18 @@ impl<'gc> Executable<'gc> { let mut arguments = Object::object(ac.gc_context); if !af.suppress_arguments { for i in 0..args.len() { - arguments.force_set(&format!("{}", i), args.get(i).unwrap().clone()) + arguments.force_set( + &format!("{}", i), + args.get(i).unwrap().clone(), + Attribute::DontDelete, + ) } - arguments.force_set("length", Value::Number(args.len() as f64)); + arguments.force_set( + "length", + Value::Number(args.len() as f64), + Attribute::DontDelete, + ); } let argcell = GcCell::allocate(ac.gc_context, arguments); diff --git a/core/src/avm1/globals.rs b/core/src/avm1/globals.rs index 1eab4bd88..ea03096d1 100644 --- a/core/src/avm1/globals.rs +++ b/core/src/avm1/globals.rs @@ -1,6 +1,7 @@ use crate::avm1::fscommand; use crate::avm1::{ActionContext, Avm1, Object, Value}; use crate::backend::navigator::NavigationMethod; +use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; use rand::Rng; @@ -52,11 +53,19 @@ pub fn random<'gc>( pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> { let mut globals = Object::object(gc_context); - globals.force_set("Math", Value::Object(math::create(gc_context))); - globals.force_set_function("getURL", getURL, gc_context); - globals.force_set_function("random", random, gc_context); - globals.force_set("NaN", Value::Number(std::f64::NAN)); - globals.force_set("Infinity", Value::Number(std::f64::INFINITY)); + globals.force_set( + "Math", + Value::Object(math::create(gc_context)), + EnumSet::empty(), + ); + globals.force_set_function("getURL", getURL, gc_context, EnumSet::empty()); + globals.force_set_function("random", random, gc_context, EnumSet::empty()); + globals.force_set("NaN", Value::Number(std::f64::NAN), EnumSet::empty()); + globals.force_set( + "Infinity", + Value::Number(std::f64::INFINITY), + EnumSet::empty(), + ); globals } diff --git a/core/src/avm1/globals/math.rs b/core/src/avm1/globals/math.rs index 6bdc4c5da..603be4bb5 100644 --- a/core/src/avm1/globals/math.rs +++ b/core/src/avm1/globals/math.rs @@ -1,3 +1,4 @@ +use crate::avm1::object::Attribute; use crate::avm1::{ActionContext, Avm1, Object, Value}; use gc_arena::{GcCell, MutationContext}; use rand::Rng; @@ -16,6 +17,7 @@ macro_rules! wrap_std { } }, $gc_context, + Attribute::DontDelete, ); )* }}; @@ -49,14 +51,46 @@ pub fn random<'gc>( pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> { let mut math = Object::object(gc_context); - math.force_set("E", Value::Number(std::f64::consts::E)); - math.force_set("LN10", Value::Number(std::f64::consts::LN_10)); - math.force_set("LN2", Value::Number(std::f64::consts::LN_2)); - math.force_set("LOG10E", Value::Number(std::f64::consts::LOG10_E)); - math.force_set("LOG2E", Value::Number(std::f64::consts::LOG2_E)); - math.force_set("PI", Value::Number(std::f64::consts::PI)); - math.force_set("SQRT1_2", Value::Number(std::f64::consts::FRAC_1_SQRT_2)); - math.force_set("SQRT2", Value::Number(std::f64::consts::SQRT_2)); + math.force_set( + "E", + Value::Number(std::f64::consts::E), + Attribute::DontDelete, + ); + math.force_set( + "LN10", + Value::Number(std::f64::consts::LN_10), + Attribute::DontDelete, + ); + math.force_set( + "LN2", + Value::Number(std::f64::consts::LN_2), + Attribute::DontDelete, + ); + math.force_set( + "LOG10E", + Value::Number(std::f64::consts::LOG10_E), + Attribute::DontDelete, + ); + math.force_set( + "LOG2E", + Value::Number(std::f64::consts::LOG2_E), + Attribute::DontDelete, + ); + math.force_set( + "PI", + Value::Number(std::f64::consts::PI), + Attribute::DontDelete, + ); + math.force_set( + "SQRT1_2", + Value::Number(std::f64::consts::FRAC_1_SQRT_2), + Attribute::DontDelete, + ); + math.force_set( + "SQRT2", + Value::Number(std::f64::consts::SQRT_2), + Attribute::DontDelete, + ); wrap_std!(math, gc_context, "abs" => f64::abs, @@ -73,8 +107,8 @@ pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<' "tan" => f64::tan ); - math.force_set_function("atan2", atan2, gc_context); - math.force_set_function("random", random, gc_context); + math.force_set_function("atan2", atan2, gc_context, Attribute::DontDelete); + math.force_set_function("random", random, gc_context, Attribute::DontDelete); GcCell::allocate(gc_context, math) } diff --git a/core/src/avm1/movie_clip.rs b/core/src/avm1/movie_clip.rs index 6359d077e..1826d7efd 100644 --- a/core/src/avm1/movie_clip.rs +++ b/core/src/avm1/movie_clip.rs @@ -1,4 +1,4 @@ -use crate::avm1::object::Object; +use crate::avm1::object::{Attribute, Object}; use crate::avm1::Value; use crate::movie_clip::MovieClip; use gc_arena::MutationContext; @@ -17,6 +17,7 @@ macro_rules! with_movie_clip { Value::Undefined }, $gc_context, + Attribute::DontDelete, ); )* }}; @@ -36,6 +37,7 @@ macro_rules! with_movie_clip_mut { Value::Undefined }, $gc_context, + Attribute::DontDelete, ); )* }}; diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index 91ce7be50..e5c8a6749 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -4,6 +4,7 @@ use crate::avm1::{ActionContext, Avm1, Value}; use crate::display_object::DisplayNode; use crate::tag_utils::SwfSlice; use core::fmt; +use enumset::{EnumSet, EnumSetType}; use gc_arena::{GcCell, MutationContext}; use std::collections::hash_map::Entry; use std::collections::HashMap; @@ -22,15 +23,23 @@ fn default_to_string<'gc>( Value::String("[Object object]".to_string()) } +#[derive(EnumSetType, Debug)] +pub enum Attribute { + DontDelete, + // DontEnum, + // ReadOnly, +} + #[derive(Clone)] pub enum Property<'gc> { Virtual { get: NativeFunction<'gc>, set: Option>, + attributes: EnumSet, }, Stored { value: Value<'gc>, - // TODO: attributes + attributes: EnumSet, }, } @@ -65,12 +74,19 @@ impl<'gc> Property<'gc> { } } } + + pub fn can_delete(&self) -> bool { + match self { + Property::Virtual { attributes, .. } => !attributes.contains(Attribute::DontDelete), + Property::Stored { attributes, .. } => !attributes.contains(Attribute::DontDelete), + } + } } unsafe impl<'gc> gc_arena::Collect for Property<'gc> { fn trace(&self, cc: gc_arena::CollectionContext) { match self { - Property::Virtual { get, set } => { + Property::Virtual { get, set, .. } => { get.trace(cc); set.trace(cc); } @@ -82,14 +98,20 @@ unsafe impl<'gc> gc_arena::Collect for Property<'gc> { impl fmt::Debug for Property<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Property::Virtual { get: _, set } => f + Property::Virtual { + get: _, + set, + attributes, + } => f .debug_struct("Property::Virtual") .field("get", &true) .field("set", &set.is_some()) + .field("attributes", &attributes) .finish(), - Property::Stored { value } => f + Property::Stored { value, attributes } => f .debug_struct("Property::Stored") .field("value", &value) + .field("attributes", &attributes) .finish(), } } @@ -129,7 +151,12 @@ impl<'gc> Object<'gc> { function: None, }; - result.force_set_function("toString", default_to_string, gc_context); + result.force_set_function( + "toString", + default_to_string, + gc_context, + Attribute::DontDelete, + ); result } @@ -208,58 +235,62 @@ impl<'gc> Object<'gc> { entry.get_mut().set(avm, context, this, value); } Entry::Vacant(entry) => { - entry.insert(Property::Stored { value }); + entry.insert(Property::Stored { + value, + attributes: Default::default(), + }); } } } - pub fn force_set_virtual( + pub fn force_set_virtual( &mut self, name: &str, get: NativeFunction<'gc>, set: Option>, - ) { - self.values - .insert(name.to_owned(), Property::Virtual { get, set }); + attributes: A, + ) where + A: Into>, + { + self.values.insert( + name.to_owned(), + Property::Virtual { + get, + set, + attributes: attributes.into(), + }, + ); } - pub fn force_set(&mut self, name: &str, value: Value<'gc>) { - self.values - .insert(name.to_string(), Property::Stored { value }); + pub fn force_set(&mut self, name: &str, value: Value<'gc>, attributes: A) + where + A: Into>, + { + self.values.insert( + name.to_string(), + Property::Stored { + value, + attributes: attributes.into(), + }, + ); } - pub fn set_native_function( - &mut self, - name: &str, - function: NativeFunction<'gc>, - avm: &mut Avm1<'gc>, - context: &mut ActionContext<'_, 'gc, '_>, - this: GcCell<'gc, Object<'gc>>, - ) { - self.set( - name, - Value::Object(GcCell::allocate( - context.gc_context, - Object::native_function(function), - )), - avm, - context, - this, - ) - } - - pub fn force_set_function( + pub fn force_set_function( &mut self, name: &str, function: NativeFunction<'gc>, gc_context: MutationContext<'gc, '_>, - ) { + attributes: A, + ) where + A: Into>, + { self.force_set( name, Value::Object(GcCell::allocate( gc_context, Object::native_function(function), )), + attributes, ) } @@ -286,8 +317,15 @@ impl<'gc> Object<'gc> { } /// Delete a given value off the object. - pub fn delete(&mut self, name: &str) { - self.values.remove(name); + pub fn delete(&mut self, name: &str) -> bool { + if let Some(prop) = self.values.get(name) { + if prop.can_delete() { + self.values.remove(name); + return true; + } + } + + false } pub fn has_property(&self, name: &str) -> bool { @@ -394,9 +432,11 @@ mod tests { #[test] fn test_set_get() { with_object(0, |avm, context, object| { - object - .write(context.gc_context) - .force_set("forced", Value::String("forced".to_string())); + object.write(context.gc_context).force_set( + "forced", + Value::String("forced".to_string()), + EnumSet::empty(), + ); object.write(context.gc_context).set( "natural", Value::String("natural".to_string()), @@ -421,9 +461,12 @@ mod tests { with_object(0, |avm, context, object| { let getter: NativeFunction = |_avm, _context, _this, _args| Value::String("Virtual!".to_string()); - object - .write(context.gc_context) - .force_set_virtual("test", getter, None); + object.write(context.gc_context).force_set_virtual( + "test", + getter, + None, + EnumSet::empty(), + ); assert_eq!( object.read().get("test", avm, context, object), @@ -444,4 +487,57 @@ mod tests { ); }) } + + #[test] + fn test_delete() { + with_object(0, |avm, context, object| { + let getter: NativeFunction = + |_avm, _context, _this, _args| Value::String("Virtual!".to_string()); + + object.write(context.gc_context).force_set_virtual( + "virtual", + getter, + None, + EnumSet::empty(), + ); + object.write(context.gc_context).force_set_virtual( + "virtual_un", + getter, + None, + Attribute::DontDelete, + ); + object.write(context.gc_context).force_set( + "stored", + Value::String("Stored!".to_string()), + EnumSet::empty(), + ); + object.write(context.gc_context).force_set( + "stored_un", + Value::String("Stored!".to_string()), + Attribute::DontDelete, + ); + + 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.read().get("virtual", avm, context, object), + Value::Undefined + ); + assert_eq!( + object.read().get("virtual_un", avm, context, object), + Value::String("Virtual!".to_string()) + ); + assert_eq!( + object.read().get("stored", avm, context, object), + Value::Undefined + ); + assert_eq!( + object.read().get("stored_un", avm, context, object), + Value::String("Stored!".to_string()) + ); + }) + } } diff --git a/core/src/avm1/scope.rs b/core/src/avm1/scope.rs index 554154cc8..f814f9de6 100644 --- a/core/src/avm1/scope.rs +++ b/core/src/avm1/scope.rs @@ -1,6 +1,7 @@ //! Represents AVM1 scope chain resolution. use crate::avm1::{ActionContext, Avm1, Object, Value}; +use enumset::EnumSet; use gc_arena::{GcCell, MutationContext}; use std::cell::{Ref, RefMut}; @@ -283,11 +284,11 @@ 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: Value<'gc>, mc: MutationContext<'gc, '_>) { - self.locals_mut(mc).force_set(name, value); + self.locals_mut(mc).force_set(name, value, EnumSet::empty()); } /// Delete a value from scope - pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) { + pub fn delete(&self, name: &str, mc: MutationContext<'gc, '_>) -> bool { if self.locals().has_property(name) { return self.locals_mut(mc).delete(name); } @@ -295,5 +296,7 @@ impl<'gc> Scope<'gc> { if let Some(scope) = self.parent() { return scope.delete(name, mc); } + + false } }