//! AVM2 values use crate::avm2::activation::Activation; use crate::avm2::names::Namespace; use crate::avm2::names::QName; use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject}; use crate::avm2::script::TranslationUnit; use crate::avm2::string::AvmString; use crate::avm2::{Avm2, Error}; use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use gc_arena::{Collect, MutationContext}; use std::cell::Ref; use swf::avm2::types::{DefaultValue as AbcDefaultValue, Index}; /// Indicate what kind of primitive coercion would be preferred when coercing /// objects. #[derive(Eq, PartialEq)] pub enum Hint { /// Prefer string coercion (e.g. call `toString` preferentially over /// `valueOf`) String, /// Prefer numerical coercion (e.g. call `valueOf` preferentially over /// `toString`) Number, } /// An AVM2 value. /// /// TODO: AVM2 also needs Scope, Namespace, and XML values. #[derive(Clone, Collect, Debug)] #[collect(no_drop)] pub enum Value<'gc> { Undefined, Null, Bool(bool), Number(f64), Unsigned(u32), Integer(i32), String(AvmString<'gc>), Object(Object<'gc>), } impl<'gc> From> for Value<'gc> { fn from(string: AvmString<'gc>) -> Self { Value::String(string) } } impl<'gc> From<&'static str> for Value<'gc> { fn from(string: &'static str) -> Self { Value::String(string.into()) } } impl<'gc> From for Value<'gc> { fn from(value: bool) -> Self { Value::Bool(value) } } impl<'gc, T> From for Value<'gc> where Object<'gc>: From, { fn from(value: T) -> Self { Value::Object(Object::from(value)) } } 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::Unsigned(u32::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: i16) -> Self { Value::Integer(i32::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: u16) -> Self { Value::Unsigned(u32::from(value)) } } impl<'gc> From for Value<'gc> { fn from(value: i32) -> Self { Value::Integer(value) } } impl<'gc> From for Value<'gc> { fn from(value: u32) -> Self { Value::Unsigned(value) } } impl<'gc> From for Value<'gc> { fn from(value: usize) -> Self { Value::Number(value as f64) } } impl PartialEq for Value<'_> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Value::Undefined, Value::Undefined) => true, (Value::Null, Value::Null) => true, (Value::Bool(a), Value::Bool(b)) => a == b, (Value::Number(a), Value::Number(b)) => a == b, (Value::Number(a), Value::Unsigned(b)) => *a == *b as f64, (Value::Number(a), Value::Integer(b)) => *a == *b as f64, (Value::Unsigned(a), Value::Number(b)) => *a as f64 == *b, (Value::Unsigned(a), Value::Unsigned(b)) => a == b, (Value::Unsigned(a), Value::Integer(b)) => *a as i64 == *b as i64, (Value::Integer(a), Value::Number(b)) => *a as f64 == *b, (Value::Integer(a), Value::Unsigned(b)) => *a as i64 == *b as i64, (Value::Integer(a), Value::Integer(b)) => a == b, (Value::String(a), Value::String(b)) => a == b, (Value::Object(a), Value::Object(b)) => Object::ptr_eq(*a, *b), _ => false, } } } pub fn abc_int(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(0); } translation_unit .abc() .constant_pool .ints .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown int constant {}", index.0).into()) } pub fn abc_uint(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(0); } translation_unit .abc() .constant_pool .uints .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown uint constant {}", index.0).into()) } pub fn abc_double(translation_unit: TranslationUnit<'_>, index: Index) -> Result { if index.0 == 0 { return Ok(f64::NAN); } translation_unit .abc() .constant_pool .doubles .get(index.0 as usize - 1) .cloned() .ok_or_else(|| format!("Unknown double constant {}", index.0).into()) } /// Retrieve a default value as an AVM2 `Value`. pub fn abc_default_value<'gc>( translation_unit: TranslationUnit<'gc>, default: &AbcDefaultValue, avm2: &mut Avm2<'gc>, mc: MutationContext<'gc, '_>, ) -> Result, Error> { match default { AbcDefaultValue::Int(i) => abc_int(translation_unit, *i).map(|v| v.into()), AbcDefaultValue::Uint(u) => abc_uint(translation_unit, *u).map(|v| v.into()), AbcDefaultValue::Double(d) => abc_double(translation_unit, *d).map(|v| v.into()), AbcDefaultValue::String(s) => translation_unit.pool_string(s.0, mc).map(|v| v.into()), AbcDefaultValue::True => Ok(true.into()), AbcDefaultValue::False => Ok(false.into()), AbcDefaultValue::Null => Ok(Value::Null), AbcDefaultValue::Undefined => Ok(Value::Undefined), AbcDefaultValue::Namespace(ns) | AbcDefaultValue::Package(ns) | AbcDefaultValue::PackageInternal(ns) | AbcDefaultValue::Protected(ns) | AbcDefaultValue::Explicit(ns) | AbcDefaultValue::StaticProtected(ns) | AbcDefaultValue::Private(ns) => Ok(NamespaceObject::from_namespace( Namespace::from_abc_namespace(translation_unit, ns.clone(), mc)?, avm2.prototypes().namespace, mc, )? .into()), } } impl<'gc> Value<'gc> { pub fn as_namespace(&self) -> Result>, Error> { match self { Value::Object(ns) => ns .as_namespace() .ok_or_else(|| "Expected Namespace, found Object".into()), _ => Err(format!("Expected Namespace, found {:?}", self).into()), } } /// Get the numerical portion of the value, if it exists. /// /// This function performs no numerical coercion, nor are user-defined /// object methods called. This should only be used if you specifically /// need the behavior of only handling actual numbers; otherwise you should /// use the appropriate `coerce_to_` method. pub fn as_number(&self, mc: MutationContext<'gc, '_>) -> Result { match self { // Methods that look for numbers in Flash Player don't seem to care // about user-defined `valueOf` implementations. This code upholds // that limitation as long as `TObject`'s `value_of` method also // does not call user-defined functions. Value::Object(num) => match num.value_of(mc)? { Value::Number(num) => Ok(num), Value::Integer(num) => Ok(num as f64), Value::Unsigned(num) => Ok(num as f64), _ => Err(format!("Expected Number, int, or uint, found {:?}", self).into()), }, Value::Number(num) => Ok(*num), Value::Integer(num) => Ok(*num as f64), Value::Unsigned(num) => Ok(*num as f64), _ => Err(format!("Expected Number, int, or uint, found {:?}", self).into()), } } /// Yields `true` if the given value is a primitive value. /// /// Note: Boxed primitive values are not considered primitive - it is /// expected that their `toString`/`valueOf` handlers have already had a /// chance to unbox the primitive contained within. pub fn is_primitive(&self) -> bool { !matches!(self, Value::Object(_)) } /// Coerce the value to a boolean. /// /// Boolean coercion happens according to the rules specified in the ES4 /// draft proposals, which appear to be identical to ECMA-262 Edition 3. pub fn coerce_to_boolean(&self) -> bool { match self { Value::Undefined | Value::Null => false, Value::Bool(b) => *b, Value::Number(f) => !f.is_nan() && *f != 0.0, Value::Unsigned(u) => *u != 0, Value::Integer(i) => *i != 0, Value::String(s) => !s.is_empty(), Value::Object(_) => true, } } /// Coerce the value to a primitive. /// /// This function is guaranteed to return either a primitive value, or a /// `TypeError`. /// /// The `Hint` parameter selects if the coercion prefers `toString` or /// `valueOf`. If the preferred function is not available, its opposite /// will be called. If neither function successfully generates a primitive, /// a `TypeError` will be raised. /// /// Primitive conversions occur according to ECMA-262 3rd Edition's /// ToPrimitive algorithm which appears to match AVM2. pub fn coerce_to_primitive( &self, hint: Option, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { let hint = hint.unwrap_or_else(|| match self { Value::Object(o) => o.default_hint(), _ => Hint::Number, }); match self { Value::Object(o) if hint == Hint::String => { let mut prim = self.clone(); let mut object = *o; if let Value::Object(f) = object.get_property(*o, &QName::dynamic_name("toString"), activation)? { prim = f.call(Some(*o), &[], activation, None)?; } if prim.is_primitive() { return Ok(prim); } if let Value::Object(f) = object.get_property(*o, &QName::dynamic_name("valueOf"), activation)? { prim = f.call(Some(*o), &[], activation, None)?; } if prim.is_primitive() { return Ok(prim); } Err("TypeError: cannot convert object to string".into()) } Value::Object(o) if hint == Hint::Number => { let mut prim = self.clone(); let mut object = *o; if let Value::Object(f) = object.get_property(*o, &QName::dynamic_name("valueOf"), activation)? { prim = f.call(Some(*o), &[], activation, None)?; } if prim.is_primitive() { return Ok(prim); } if let Value::Object(f) = object.get_property(*o, &QName::dynamic_name("toString"), activation)? { prim = f.call(Some(*o), &[], activation, None)?; } if prim.is_primitive() { return Ok(prim); } Err("TypeError: cannot convert object to number".into()) } _ => Ok(self.clone()), } } /// Coerce the value to a floating-point number. /// /// This function returns the resulting floating-point directly; or a /// TypeError if the value is an `Object` that cannot be converted to a /// primitive value. /// /// Numerical conversions occur according to ECMA-262 3rd Edition's /// ToNumber algorithm which appears to match AVM2. pub fn coerce_to_number(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result { Ok(match self { Value::Undefined => f64::NAN, Value::Null => 0.0, Value::Bool(true) => 1.0, Value::Bool(false) => 0.0, Value::Number(n) => *n, Value::Unsigned(u) => *u as f64, Value::Integer(i) => *i as f64, Value::String(s) => { let strim = s.trim(); if strim.is_empty() { 0.0 } else if strim.starts_with("0x") || strim.starts_with("0X") { let mut n: f64 = 0.0; for c in strim[2..].chars() { n *= 16.0; n += match c { '0' => 0.0, '1' => 1.0, '2' => 2.0, '3' => 3.0, '4' => 4.0, '5' => 5.0, '6' => 6.0, '7' => 7.0, '8' => 8.0, '9' => 9.0, 'a' | 'A' => 10.0, 'b' | 'B' => 11.0, 'c' | 'C' => 12.0, 'd' | 'D' => 13.0, 'e' | 'E' => 14.0, 'f' | 'F' => 15.0, _ => return Ok(f64::NAN), }; } n } else { let (sign, digits) = if let Some(stripped) = strim.strip_prefix('+') { (1.0, stripped) } else if let Some(stripped) = strim.strip_prefix('-') { (-1.0, stripped) } else { (1.0, strim) }; if digits == "Infinity" { return Ok(sign * f64::INFINITY); } else if digits.starts_with(['i', 'I'].as_ref()) { // Avoid Rust f64::parse accepting "inf" and "infinity" return Ok(f64::NAN); } //TODO: This is slightly more permissive than ES3 spec, as //Rust documentation claims it will accept "inf" as f64 //infinity. sign * digits.parse().unwrap_or(f64::NAN) } } Value::Object(_) => self .coerce_to_primitive(Some(Hint::Number), activation)? .coerce_to_number(activation)?, }) } /// Coerce the value to a 32-bit unsigned integer. /// /// This function returns the resulting u32 directly; or a TypeError if the /// value is an `Object` that cannot be converted to a primitive value. /// /// Numerical conversions occur according to ECMA-262 3rd Edition's /// ToUint32 algorithm which appears to match AVM2. pub fn coerce_to_u32(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result { Ok(f64_to_wrapping_u32(self.coerce_to_number(activation)?)) } /// Coerce the value to a 32-bit signed integer. /// /// This function returns the resulting i32 directly; or a TypeError if the /// value is an `Object` that cannot be converted to a primitive value. /// /// Numerical conversions occur according to ECMA-262 3rd Edition's /// ToInt32 algorithm which appears to match AVM2. pub fn coerce_to_i32(&self, activation: &mut Activation<'_, 'gc, '_>) -> Result { Ok(f64_to_wrapping_i32(self.coerce_to_number(activation)?)) } /// Minimum number of digits after which numbers are formatted as /// exponential strings. const MIN_DIGITS: f64 = -6.0; /// Maximum number of digits before numbers are formatted as exponential /// strings. const MAX_DIGITS: f64 = 21.0; /// Maximum number of significant digits renderable within coerced numbers. /// /// Any precision beyond this point will be discarded and replaced with /// zeroes (for whole parts) or not rendered (for decimal parts). const MAX_PRECISION: f64 = 15.0; /// Coerce the value to a String. /// /// This function returns the resulting String directly; or a TypeError if /// the value is an `Object` that cannot be converted to a primitive value. /// /// String conversions generally occur according to ECMA-262 3rd Edition's /// ToString algorithm. The conversion of numbers to strings appears to be /// somewhat underspecified; there are several formatting modes which /// change at specific digit count cutoffs, but the spec allows /// implementations to limit how much precision is displayed on coerced /// numbers, even if that precision would result in rounding the whole part /// of the number. (This is confusingly expressed in ECMA-262.) /// /// TODO: The cutoffs change based on SWF/ABC version. Targeting FP10.3 in /// Animate CC 2020 significantly reduces them (towards zero). pub fn coerce_to_string<'a>( &'a self, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { Ok(match self { Value::Undefined => "undefined".into(), Value::Null => "null".into(), Value::Bool(true) => "true".into(), Value::Bool(false) => "false".into(), Value::Number(n) if n.is_nan() => "NaN".into(), Value::Number(n) if *n == 0.0 => "0".into(), Value::Number(n) if *n < 0.0 => AvmString::new( activation.context.gc_context, format!("-{}", Value::Number(-n).coerce_to_string(activation)?), ), Value::Number(n) if n.is_infinite() => "Infinity".into(), Value::Number(n) => { let digits = n.log10().floor(); // TODO: This needs to limit precision in the resulting decimal // output, not in binary. let precision = (n * 10.0_f64.powf(Self::MAX_PRECISION - digits)).floor() / 10.0_f64.powf(Self::MAX_PRECISION - digits); if digits < Self::MIN_DIGITS || digits >= Self::MAX_DIGITS { AvmString::new( activation.context.gc_context, format!( "{}e{}{}", precision / 10.0_f64.powf(digits), if digits < 0.0 { "-" } else { "+" }, digits.abs() ), ) } else { AvmString::new(activation.context.gc_context, format!("{}", n)) } } Value::Unsigned(u) => AvmString::new(activation.context.gc_context, format!("{}", u)), Value::Integer(i) => AvmString::new(activation.context.gc_context, format!("{}", i)), Value::String(s) => *s, Value::Object(_) => self .coerce_to_primitive(Some(Hint::String), activation)? .coerce_to_string(activation)?, }) } /// Coerce the value to a literal value / debug string. /// /// This matches the string formatting that appears to be in use in "debug" /// contexts, where strings themselves also get quoted. Such contexts would /// include things like `valueOf`/`toString` on classes that expose their /// properties as part of the string. pub fn coerce_to_debug_string<'a>( &'a self, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { Ok(match self { Value::String(s) => AvmString::new(activation.context.gc_context, format!("\"{}\"", s)), Value::Object(_) => self .coerce_to_primitive(Some(Hint::String), activation)? .coerce_to_debug_string(activation)?, _ => self.coerce_to_string(activation)?, }) } /// Coerce the value to an Object. /// /// TODO: In ECMA-262 3rd Edition, this would also box primitive values /// into objects. Supposedly, ES4 removes primitive values entirely, and /// the AVM2 Overview also implies that all this does is throw an error if /// `undefined` or `null` are present. For the time being, this is what /// that does. If we implement primitive boxing, then we should also box /// them here, and this should change type to return `Object<'gc>`. pub fn coerce_to_object( &self, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { match self { Value::Undefined => return Err("TypeError: undefined is not an Object".into()), Value::Null => return Err("TypeError: null is not an Object".into()), Value::Object(o) => return Ok(*o), _ => {} }; let proto = match self { Value::Bool(_) => activation.avm2().prototypes().boolean, Value::Number(_) => activation.avm2().prototypes().number, Value::Unsigned(_) => activation.avm2().prototypes().uint, Value::Integer(_) => activation.avm2().prototypes().int, Value::String(_) => activation.avm2().prototypes().string, _ => unreachable!(), }; PrimitiveObject::from_primitive(self.clone(), proto, activation.context.gc_context) } /// Determine if two values are abstractly equal to each other. /// /// This abstract equality algorithm is intended to match ECMA-262 3rd /// edition, section 11.9.3. Inequality is the direct opposite of equality, /// and this function always returns a boolean. pub fn abstract_eq( &self, other: &Value<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result { match (self, other) { (Value::Undefined, Value::Undefined) => Ok(true), (Value::Null, Value::Null) => Ok(true), (Value::Number(_), Value::Number(_)) | (Value::Number(_), Value::Unsigned(_)) | (Value::Number(_), Value::Integer(_)) | (Value::Unsigned(_), Value::Number(_)) | (Value::Integer(_), Value::Number(_)) => { let a = self.coerce_to_number(activation)?; let b = other.coerce_to_number(activation)?; if a.is_nan() || b.is_nan() { return Ok(false); } if a == b { return Ok(true); } if a.abs() == 0.0 && b.abs() == 0.0 { return Ok(true); } Ok(false) } (Value::Unsigned(a), Value::Unsigned(b)) => Ok(a == b), (Value::Unsigned(a), Value::Integer(b)) => Ok(*a as i64 == *b as i64), (Value::Integer(a), Value::Unsigned(b)) => Ok(*a as i64 == *b as i64), (Value::Integer(a), Value::Integer(b)) => Ok(a == b), (Value::String(a), Value::String(b)) => Ok(a == b), (Value::Bool(a), Value::Bool(b)) => Ok(a == b), (Value::Object(a), Value::Object(b)) => Ok(Object::ptr_eq(*a, *b)), (Value::Undefined, Value::Null) => Ok(true), (Value::Null, Value::Undefined) => Ok(true), (Value::Number(_), Value::String(_)) | (Value::Unsigned(_), Value::String(_)) | (Value::Integer(_), Value::String(_)) => { let number_other = Value::from(other.coerce_to_number(activation)?); self.abstract_eq(&number_other, activation) } (Value::String(_), Value::Number(_)) | (Value::String(_), Value::Unsigned(_)) | (Value::String(_), Value::Integer(_)) => { let number_self = Value::from(self.coerce_to_number(activation)?); number_self.abstract_eq(other, activation) } (Value::Bool(_), _) => { let number_self = Value::from(self.coerce_to_number(activation)?); number_self.abstract_eq(other, activation) } (_, Value::Bool(_)) => { let number_other = Value::from(other.coerce_to_number(activation)?); self.abstract_eq(&number_other, activation) } (Value::String(_), Value::Object(_)) | (Value::Number(_), Value::Object(_)) | (Value::Unsigned(_), Value::Object(_)) | (Value::Integer(_), Value::Object(_)) => { //TODO: Should this be `Hint::Number`, `Hint::String`, or no-hint? let primitive_other = other.coerce_to_primitive(Some(Hint::Number), activation)?; self.abstract_eq(&primitive_other, activation) } (Value::Object(_), Value::String(_)) | (Value::Object(_), Value::Number(_)) | (Value::Object(_), Value::Unsigned(_)) | (Value::Object(_), Value::Integer(_)) => { //TODO: Should this be `Hint::Number`, `Hint::String`, or no-hint? let primitive_self = self.coerce_to_primitive(Some(Hint::Number), activation)?; primitive_self.abstract_eq(other, activation) } _ => Ok(false), } } /// Determine if this value is abstractly less than the other. /// /// This abstract relational comparison algorithm is intended to match /// ECMA-262 3rd edition, section 11.8.5. It returns `true`, `false`, *or* /// `undefined` (to signal NaN), the latter of which we represent as `None`. #[allow(clippy::float_cmp)] pub fn abstract_lt( &self, other: &Value<'gc>, activation: &mut Activation<'_, 'gc, '_>, ) -> Result, Error> { let prim_self = self.coerce_to_primitive(Some(Hint::Number), activation)?; let prim_other = other.coerce_to_primitive(Some(Hint::Number), activation)?; if let (Value::String(s), Value::String(o)) = (&prim_self, &prim_other) { return Ok(Some(s.to_string().bytes().lt(o.to_string().bytes()))); } let num_self = prim_self.coerce_to_number(activation)?; let num_other = prim_other.coerce_to_number(activation)?; if num_self.is_nan() || num_other.is_nan() { return Ok(None); } Ok(Some(num_self < num_other)) } }