ruffle/core/src/avm2/value.rs

692 lines
25 KiB
Rust

//! 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 enumset::{EnumSet, EnumSetType};
use gc_arena::{Collect, MutationContext};
use std::cell::Ref;
use std::f64::NAN;
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<AvmString<'gc>> 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<bool> for Value<'gc> {
fn from(value: bool) -> Self {
Value::Bool(value)
}
}
impl<'gc, T> From<T> for Value<'gc>
where
Object<'gc>: From<T>,
{
fn from(value: T) -> Self {
Value::Object(Object::from(value))
}
}
impl<'gc> From<f64> for Value<'gc> {
fn from(value: f64) -> Self {
Value::Number(value)
}
}
impl<'gc> From<f32> for Value<'gc> {
fn from(value: f32) -> Self {
Value::Number(f64::from(value))
}
}
impl<'gc> From<u8> for Value<'gc> {
fn from(value: u8) -> Self {
Value::Unsigned(u32::from(value))
}
}
impl<'gc> From<i16> for Value<'gc> {
fn from(value: i16) -> Self {
Value::Integer(i32::from(value))
}
}
impl<'gc> From<u16> for Value<'gc> {
fn from(value: u16) -> Self {
Value::Unsigned(u32::from(value))
}
}
impl<'gc> From<i32> for Value<'gc> {
fn from(value: i32) -> Self {
Value::Integer(value)
}
}
impl<'gc> From<u32> for Value<'gc> {
fn from(value: u32) -> Self {
Value::Unsigned(value)
}
}
impl<'gc> From<usize> 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<i32>) -> Result<i32, Error> {
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<u32>) -> Result<u32, Error> {
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<f64>) -> Result<f64, Error> {
if index.0 == 0 {
return Ok(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<Value<'gc>, 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<Ref<Namespace<'gc>>, 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<f64, Error> {
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<Hint>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Value<'gc>, 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<f64, Error> {
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);
}
//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<u32, Error> {
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<i32, Error> {
Ok(f64_to_wrapping_i32(self.coerce_to_number(activation)?))
}
/// Coerce the value to an EnumSet of a particular type.
///
/// This function will ignore invalid bits of the value when interpreting
/// the enum.
pub fn coerce_to_enumset<E>(
&self,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<EnumSet<E>, Error>
where
E: EnumSetType,
{
Ok(EnumSet::from_u32_truncated(self.coerce_to_u32(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<AvmString<'gc>, 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 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<Object<'gc>, 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!(),
};
Ok(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<bool, Error> {
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<Option<bool>, 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))
}
}