use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Error, ObjectCell, UpdateContext}; use gc_arena::GcCell; use std::f64::NAN; #[derive(Clone, Debug)] #[allow(dead_code)] pub enum Value<'gc> { Undefined, Null, Bool(bool), Number(f64), String(String), Object(ObjectCell<'gc>), } impl<'gc> From for Value<'gc> { fn from(string: String) -> Self { Value::String(string) } } impl<'gc> From<&str> for Value<'gc> { fn from(string: &str) -> Self { Value::String(string.to_owned()) } } impl<'gc> From for Value<'gc> { fn from(value: bool) -> Self { Value::Bool(value) } } impl<'gc> From> for Value<'gc> { fn from(object: ObjectCell<'gc>) -> Self { Value::Object(object) } } impl<'gc> From for Value<'gc> { fn from(value: f64) -> Self { Value::Number(value) } } impl<'gc> From for Value<'gc> { fn from(value: f32) -> Self { Value::Number(f64::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: u8) -> Self { Value::Number(f64::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: i32) -> Self { Value::Number(f64::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: u32) -> Self { Value::Number(f64::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: usize) -> Self { Value::Number(value as f64) } } unsafe impl<'gc> gc_arena::Collect for Value<'gc> { fn trace(&self, cc: gc_arena::CollectionContext) { if let Value::Object(object) = self { object.trace(cc); } } } impl PartialEq for Value<'_> { fn eq(&self, other: &Self) -> bool { match self { Value::Undefined => match other { Value::Undefined => true, _ => false, }, Value::Null => match other { Value::Null => true, _ => false, }, Value::Bool(value) => match other { Value::Bool(other_value) => value == other_value, _ => false, }, Value::Number(value) => match other { Value::Number(other_value) => { (value == other_value) || (value.is_nan() && other_value.is_nan()) } _ => false, }, Value::String(value) => match other { Value::String(other_value) => value == other_value, _ => false, }, Value::Object(value) => match other { Value::Object(other_value) => value.as_ptr() == other_value.as_ptr(), _ => false, }, } } } impl<'gc> Value<'gc> { pub fn into_number_v1(self) -> f64 { match self { Value::Bool(true) => 1.0, Value::Number(v) => v, Value::String(v) => v.parse().unwrap_or(0.0), _ => 0.0, } } /// ECMA-262 2nd edtion s. 9.3 ToNumber (after calling `to_primitive_num`) /// /// Flash diverges from spec in a number of ways. These ways are, as far as /// we are aware, version-gated: /// /// * In SWF6 and lower, `undefined` is coerced to `0.0` (like `false`) /// rathern than `NaN` as required by spec. fn primitive_as_number( &self, avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, ) -> f64 { match self { Value::Undefined if avm.current_swf_version() < 7 => 0.0, Value::Null if avm.current_swf_version() < 7 => 0.0, Value::Undefined => NAN, Value::Null => NAN, Value::Bool(false) => 0.0, Value::Bool(true) => 1.0, Value::Number(v) => *v, Value::String(v) => match v.as_str() { v if v.starts_with("0x") => { let mut n: u32 = 0; for c in v[2..].bytes() { n = n.wrapping_shl(4); n |= match c { b'0' => 0, b'1' => 1, b'2' => 2, b'3' => 3, b'4' => 4, b'5' => 5, b'6' => 6, b'7' => 7, b'8' => 8, b'9' => 9, b'a' | b'A' => 10, b'b' | b'B' => 11, b'c' | b'C' => 12, b'd' | b'D' => 13, b'e' | b'E' => 14, b'f' | b'F' => 15, _ => return NAN, } } f64::from(n as i32) } "" => 0.0, _ => v.parse().unwrap_or(NAN), }, Value::Object(_) => NAN, } } /// ECMA-262 2nd edition s. 9.3 ToNumber pub fn as_number( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result { Ok(match self { Value::Object(_) => self .to_primitive_num(avm, context)? .primitive_as_number(avm, context), val => val.primitive_as_number(avm, context), }) } /// ECMA-262 2nd edition s. 9.1 ToPrimitive (hint: Number) /// /// Flash diverges from spec in a number of ways. These ways are, as far as /// we are aware, version-gated: /// /// * `toString` is never called when `ToPrimitive` is invoked with number /// hint. /// * Objects with uncallable `valueOf` implementations are coerced to /// `undefined`. This is not a special-cased behavior: All values are /// callable in `AVM1`. Values that are not callable objects instead /// return `undefined` rather than yielding a runtime error. pub fn to_primitive_num( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { Ok(match self { Value::Object(object) => { let value_of_impl = object .read() .get("valueOf", avm, context, *object)? .resolve(avm, context)?; let fake_args = Vec::new(); value_of_impl .call(avm, context, *object, &fake_args)? .resolve(avm, context)? } val => val.to_owned(), }) } /// ECMA-262 2nd edition s. 11.8.5 Abstract relational comparison algorithm #[allow(clippy::float_cmp)] pub fn abstract_lt( &self, other: Value<'gc>, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result, Error> { let prim_self = self.to_primitive_num(avm, context)?; let prim_other = other.to_primitive_num(avm, context)?; if let (Value::String(a), Value::String(b)) = (&prim_self, &prim_other) { return Ok(a.to_string().bytes().lt(b.to_string().bytes()).into()); } let num_self = prim_self.primitive_as_number(avm, context); let num_other = prim_other.primitive_as_number(avm, context); if num_self.is_nan() || num_other.is_nan() { return Ok(Value::Undefined); } if num_self == num_other || num_self == 0.0 && num_other == -0.0 || num_self == -0.0 && num_other == 0.0 || num_self.is_infinite() && num_self.is_sign_positive() || num_other.is_infinite() && num_other.is_sign_negative() { return Ok(false.into()); } if num_self.is_infinite() && num_self.is_sign_negative() || num_other.is_infinite() && num_other.is_sign_positive() { return Ok(true.into()); } Ok((num_self < num_other).into()) } /// ECMA-262 2nd edition s. 11.9.3 Abstract equality comparison algorithm pub fn abstract_eq( &self, other: Value<'gc>, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, coerced: bool, ) -> Result, Error> { match (self, &other) { (Value::Undefined, Value::Undefined) => Ok(true.into()), (Value::Null, Value::Null) => Ok(true.into()), (Value::Number(a), Value::Number(b)) => { if !coerced && a.is_nan() && b.is_nan() { return Ok(true.into()); } if a == b { return Ok(true.into()); } if *a == 0.0 && *b == -0.0 || *a == -0.0 && *b == 0.0 { return Ok(true.into()); } Ok(false.into()) } (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::Null) | (Value::Object(a), Value::Undefined) => { Ok((a.as_ptr() == avm.global_object_cell().as_ptr()).into()) } (Value::Null, Value::Object(b)) | (Value::Undefined, Value::Object(b)) => { Ok((b.as_ptr() == avm.global_object_cell().as_ptr()).into()) } (Value::Undefined, Value::Null) => Ok(true.into()), (Value::Null, Value::Undefined) => Ok(true.into()), (Value::Number(_), Value::String(_)) => Ok(self.abstract_eq( Value::Number(other.as_number(avm, context)?), avm, context, true, )?), (Value::String(_), Value::Number(_)) => { Ok(Value::Number(self.as_number(avm, context)?) .abstract_eq(other, avm, context, true)?) } (Value::Bool(_), _) => Ok(Value::Number(self.as_number(avm, context)?) .abstract_eq(other, avm, context, true)?), (_, Value::Bool(_)) => Ok(self.abstract_eq( Value::Number(other.as_number(avm, context)?), avm, context, true, )?), (Value::String(_), Value::Object(_)) => { let non_obj_other = other.to_primitive_num(avm, context)?; if let Value::Object(_) = non_obj_other { return Ok(false.into()); } Ok(self.abstract_eq(non_obj_other, avm, context, true)?) } (Value::Number(_), Value::Object(_)) => { let non_obj_other = other.to_primitive_num(avm, context)?; if let Value::Object(_) = non_obj_other { return Ok(false.into()); } Ok(self.abstract_eq(non_obj_other, avm, context, true)?) } (Value::Object(_), Value::String(_)) => { let non_obj_self = self.to_primitive_num(avm, context)?; if let Value::Object(_) = non_obj_self { return Ok(false.into()); } Ok(non_obj_self.abstract_eq(other, avm, context, true)?) } (Value::Object(_), Value::Number(_)) => { let non_obj_self = self.to_primitive_num(avm, context)?; if let Value::Object(_) = non_obj_self { return Ok(false.into()); } Ok(non_obj_self.abstract_eq(other, avm, context, true)?) } _ => Ok(false.into()), } } pub fn from_bool_v1(value: bool, swf_version: u8) -> Value<'gc> { // SWF version 4 did not have true bools and will push bools as 0 or 1. // e.g. SWF19 p. 72: // "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack." if swf_version >= 5 { Value::Bool(value) } else { Value::Number(if value { 1.0 } else { 0.0 }) } } /// Coerce a value to a string without calling object methods. pub fn into_string(self) -> String { match self { Value::Undefined => "undefined".to_string(), Value::Null => "null".to_string(), 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(), } } /// Coerce a value to a string. pub fn coerce_to_string( self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result { 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(); match to_string_impl .call(avm, context, object, &fake_args)? .resolve(avm, context)? { Value::String(s) => s, _ => "[type Object]".to_string(), } } _ => self.into_string(), }) } pub fn as_bool(&self, swf_version: u8) -> bool { match self { Value::Bool(v) => *v, Value::Number(v) => !v.is_nan() && *v != 0.0, Value::String(v) => { if swf_version >= 7 { !v.is_empty() } else { let num = v.parse().unwrap_or(0.0); num != 0.0 } } Value::Object(_) => true, _ => false, } } pub fn type_of(&self) -> Value<'gc> { Value::String( match self { Value::Undefined => "undefined", Value::Null => "null", Value::Number(_) => "number", Value::Bool(_) => "boolean", Value::String(_) => "string", Value::Object(object) => object.read().type_of(), } .to_string(), ) } pub fn as_i32(&self) -> Result { self.as_f64().map(|n| n as i32) } pub fn as_u32(&self) -> Result { self.as_f64().map(|n| n as u32) } pub fn as_i64(&self) -> Result { self.as_f64().map(|n| n as i64) } pub fn as_usize(&self) -> Result { self.as_f64().map(|n| n as usize) } pub fn as_f64(&self) -> Result { match *self { Value::Number(v) => Ok(v), _ => Err(format!("Expected Number, found {:?}", self).into()), } } pub fn as_string(&self) -> Result<&String, Error> { match self { Value::String(s) => Ok(s), _ => Err(format!("Expected String, found {:?}", self).into()), } } pub fn as_object(&self) -> Result, Error> { if let Value::Object(object) = self { Ok(object.to_owned()) } else { Err(format!("Expected Object, found {:?}", self).into()) } } pub fn call( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: ObjectCell<'gc>, args: &[Value<'gc>], ) -> Result, Error> { if let Value::Object(object) = self { object.read().call(avm, context, this, args) } else { Ok(Value::Undefined.into()) } } } #[cfg(test)] mod test { use crate::avm1::function::Executable; use crate::avm1::globals::create_globals; use crate::avm1::object::ObjectCell; 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] fn to_primitive_num() { with_avm(6, |avm, context, _this| { let t = Value::Bool(true); let u = Value::Undefined; let f = Value::Bool(false); let n = Value::Null; assert_eq!(t.to_primitive_num(avm, context).unwrap(), t); assert_eq!(u.to_primitive_num(avm, context).unwrap(), u); assert_eq!(f.to_primitive_num(avm, context).unwrap(), f); assert_eq!(n.to_primitive_num(avm, context).unwrap(), n); let (protos, global) = create_globals(context.gc_context); let vglobal = Value::Object(global); assert_eq!(vglobal.to_primitive_num(avm, context).unwrap(), u); fn value_of_impl<'gc>( _: &mut Avm1<'gc>, _: &mut UpdateContext<'_, 'gc, '_>, _: ObjectCell<'gc>, _: &[Value<'gc>], ) -> Result, Error> { Ok(5.0.into()) } let valueof = ScriptObject::function( context.gc_context, Executable::Native(value_of_impl), Some(protos.function), None, ); let o = ScriptObject::object_cell(context.gc_context, Some(protos.object)); o.write(context.gc_context) .define_value("valueOf", valueof.into(), EnumSet::empty()); assert_eq!( Value::Object(o).to_primitive_num(avm, context).unwrap(), Value::Number(5.0) ); }); } #[test] #[allow(clippy::float_cmp)] fn to_number_swf7() { with_avm(7, |avm, context, _this| { let t = Value::Bool(true); let u = Value::Undefined; let f = Value::Bool(false); let n = Value::Null; assert_eq!(t.as_number(avm, context).unwrap(), 1.0); assert!(u.as_number(avm, context).unwrap().is_nan()); 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()), )); assert!(bo.as_number(avm, context).unwrap().is_nan()); }); } #[test] #[allow(clippy::float_cmp)] fn to_number_swf6() { with_avm(6, |avm, context, _this| { let t = Value::Bool(true); let u = Value::Undefined; let f = Value::Bool(false); let n = Value::Null; assert_eq!(t.as_number(avm, context).unwrap(), 1.0); assert_eq!(u.as_number(avm, context).unwrap(), 0.0); 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()), )); assert_eq!(bo.as_number(avm, context).unwrap(), 0.0); }); } #[test] fn abstract_lt_num() { with_avm(8, |avm, context, _this| { let a = Value::Number(1.0); let b = Value::Number(2.0); assert_eq!(a.abstract_lt(b, avm, context).unwrap(), Value::Bool(true)); let nan = Value::Number(NAN); assert_eq!(a.abstract_lt(nan, avm, context).unwrap(), Value::Undefined); let inf = Value::Number(INFINITY); assert_eq!(a.abstract_lt(inf, avm, context).unwrap(), Value::Bool(true)); let neg_inf = Value::Number(NEG_INFINITY); assert_eq!( a.abstract_lt(neg_inf, avm, context).unwrap(), Value::Bool(false) ); let zero = Value::Number(0.0); assert_eq!( a.abstract_lt(zero, avm, context).unwrap(), Value::Bool(false) ); }); } #[test] fn abstract_gt_num() { with_avm(8, |avm, context, _this| { let a = Value::Number(1.0); let b = Value::Number(2.0); assert_eq!( b.abstract_lt(a.clone(), avm, context).unwrap(), Value::Bool(false) ); let nan = Value::Number(NAN); assert_eq!( nan.abstract_lt(a.clone(), avm, context).unwrap(), Value::Undefined ); let inf = Value::Number(INFINITY); assert_eq!( inf.abstract_lt(a.clone(), avm, context).unwrap(), Value::Bool(false) ); let neg_inf = Value::Number(NEG_INFINITY); assert_eq!( neg_inf.abstract_lt(a.clone(), avm, context).unwrap(), Value::Bool(true) ); let zero = Value::Number(0.0); assert_eq!( zero.abstract_lt(a, avm, context).unwrap(), Value::Bool(true) ); }); } #[test] fn abstract_lt_str() { with_avm(8, |avm, context, _this| { let a = Value::String("a".to_owned()); let b = Value::String("b".to_owned()); assert_eq!(a.abstract_lt(b, avm, context).unwrap(), Value::Bool(true)) }) } #[test] fn abstract_gt_str() { with_avm(8, |avm, context, _this| { let a = Value::String("a".to_owned()); let b = Value::String("b".to_owned()); assert_eq!(b.abstract_lt(a, avm, context).unwrap(), Value::Bool(false)) }) } }