Added object property attributes (initially just DontDelete)

This commit is contained in:
Nathan Adams 2019-10-08 14:21:07 +02:00
parent 4856efe7d8
commit f2a4000ee2
8 changed files with 229 additions and 71 deletions

View File

@ -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"

View File

@ -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(())
}

View File

@ -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);

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
);
)*
}};

View File

@ -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<NativeFunction<'gc>>,
attributes: EnumSet<Attribute>,
},
Stored {
value: Value<'gc>,
// TODO: attributes
attributes: EnumSet<Attribute>,
},
}
@ -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<A>(
&mut self,
name: &str,
get: NativeFunction<'gc>,
set: Option<NativeFunction<'gc>>,
) {
self.values
.insert(name.to_owned(), Property::Virtual { get, set });
attributes: A,
) where
A: Into<EnumSet<Attribute>>,
{
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<A>(&mut self, name: &str, value: Value<'gc>, attributes: A)
where
A: Into<EnumSet<Attribute>>,
{
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<A>(
&mut self,
name: &str,
function: NativeFunction<'gc>,
gc_context: MutationContext<'gc, '_>,
) {
attributes: A,
) where
A: Into<EnumSet<Attribute>>,
{
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) {
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())
);
})
}
}

View File

@ -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
}
}