avm2: Make `coerce_to_type` take a class object instead of a multiname.

This commit is contained in:
David Wendt 2021-06-24 20:08:33 -06:00
parent bd2f758976
commit 2c927f2b6b
3 changed files with 74 additions and 55 deletions

View File

@ -199,6 +199,30 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
}) })
} }
/// Resolve a type name to a class.
///
/// This returns an error if a type is named but does not exist; or if the
/// typed named is not a class object.
fn resolve_type(&mut self, type_name: Multiname<'gc>) -> Result<Option<Object<'gc>>, Error> {
if type_name.is_any() {
return Ok(None);
}
let class = self
.scope()
.ok_or("Cannot resolve parameter types without a scope stack")?
.write(self.context.gc_context)
.resolve(&type_name, self)?
.ok_or_else(|| format!("Could not resolve parameter type {:?}", type_name))?
.coerce_to_object(self)?;
if class.as_class().is_none() {
return Err(format!("Resolved parameter type {:?} is not a class", type_name).into());
}
Ok(Some(class))
}
/// Resolve a single parameter value. /// Resolve a single parameter value.
/// ///
/// Given an individual parameter value and the associated parameter's /// Given an individual parameter value and the associated parameter's
@ -226,7 +250,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
.into()); .into());
}; };
arg.coerce_to_type(self, param_config.param_type_name.clone()) let type_name = param_config.param_type_name.clone();
let param_type = self.resolve_type(type_name)?;
if let Some(param_type) = param_type {
arg.coerce_to_type(self, param_type)
} else {
Ok(arg.into_owned())
}
} }
/// Statically resolve all of the parameters for a given method. /// Statically resolve all of the parameters for a given method.
@ -2606,8 +2637,13 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
) -> Result<FrameControl<'gc>, Error> { ) -> Result<FrameControl<'gc>, Error> {
let val = self.context.avm2.pop(); let val = self.context.avm2.pop();
let type_name = self.pool_multiname_static_any(method, index)?; let type_name = self.pool_multiname_static_any(method, index)?;
let param_type = self.resolve_type(type_name)?;
let x = val.coerce_to_type(self, type_name)?; let x = if let Some(param_type) = param_type {
val.coerce_to_type(self, param_type)?
} else {
val
};
self.context.avm2.push(x); self.context.avm2.push(x);
Ok(FrameControl::Continue) Ok(FrameControl::Continue)

View File

@ -295,20 +295,11 @@ impl<'gc> TObject<'gc> for ClassObject<'gc> {
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_superclass_object: Option<Object<'gc>>, _superclass_object: Option<Object<'gc>>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
let class_name = self
.as_class()
.ok_or("Attempted to cast to class object that is missing a class!")?
.read()
.name()
.clone()
.into();
log::error!("{:?}", class_name);
arguments arguments
.get(0) .get(0)
.cloned() .cloned()
.unwrap_or(Value::Undefined) .unwrap_or(Value::Undefined)
.coerce_to_type(activation, class_name) .coerce_to_type(activation, self.into())
} }
fn call_init( fn call_init(

View File

@ -1,7 +1,6 @@
//! AVM2 values //! AVM2 values
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::names::Multiname;
use crate::avm2::names::Namespace; use crate::avm2::names::Namespace;
use crate::avm2::names::QName; use crate::avm2::names::QName;
use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject}; use crate::avm2::object::{NamespaceObject, Object, PrimitiveObject, TObject};
@ -572,63 +571,56 @@ impl<'gc> Value<'gc> {
/// Coerce the value to another value by type name. /// Coerce the value to another value by type name.
/// ///
/// This function implements a handful of coercion rules that appear to be /// This function implements a handful of coercion rules that appear to be
/// in use when parameters are typechecked. I suspect `op_coerce` also uses /// in use when parameters are typechecked. `op_coerce` appears to use
/// these, but I cannot typecheck this for certain. /// these as well. If `class` is the class corresponding to a primitive
/// /// type, then this function will coerce the given value to that type.
/// If `type_name` matches a primitive type, additional primitive coercions
/// will apply to the resulting value. This relies on the fact that you
/// cannot redefine default types, so `int` always refers to an int.
/// ///
/// If the type is not coercible to the given type, an error is thrown. /// If the type is not coercible to the given type, an error is thrown.
pub fn coerce_to_type( pub fn coerce_to_type(
&self, &self,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
type_name: Multiname<'gc>, class: Object<'gc>,
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if !type_name.is_any() { if Object::ptr_eq(class, activation.avm2().classes().int) {
if type_name.contains_name(&QName::new(Namespace::public(), "int")) { return Ok(self.coerce_to_i32(activation)?.into());
return Ok(self.coerce_to_i32(activation)?.into()); }
}
if type_name.contains_name(&QName::new(Namespace::public(), "uint")) { if Object::ptr_eq(class, activation.avm2().classes().uint) {
return Ok(self.coerce_to_u32(activation)?.into()); return Ok(self.coerce_to_u32(activation)?.into());
} }
if type_name.contains_name(&QName::new(Namespace::public(), "Number")) { if Object::ptr_eq(class, activation.avm2().classes().number) {
return Ok(self.coerce_to_number(activation)?.into()); return Ok(self.coerce_to_number(activation)?.into());
} }
if type_name.contains_name(&QName::new(Namespace::public(), "Boolean")) { if Object::ptr_eq(class, activation.avm2().classes().boolean) {
return Ok(self.coerce_to_boolean().into()); return Ok(self.coerce_to_boolean().into());
} }
if matches!(self, Value::Undefined) || matches!(self, Value::Null) { if matches!(self, Value::Undefined) || matches!(self, Value::Null) {
return Ok(Value::Null); return Ok(Value::Null);
} }
if type_name.contains_name(&QName::new(Namespace::public(), "String")) { if Object::ptr_eq(class, activation.avm2().classes().string) {
return Ok(self.coerce_to_string(activation)?.into()); return Ok(self.coerce_to_string(activation)?.into());
} }
if let Ok(object) = self.coerce_to_object(activation) { if let Ok(object) = self.coerce_to_object(activation) {
let param_type = activation if object.is_of_type(class)? {
.scope() return Ok(object.into());
.ok_or("Cannot resolve parameter types without a scope stack")?
.write(activation.context.gc_context)
.resolve(&type_name, activation)?
.ok_or_else(|| format!("Could not resolve parameter type {:?}", type_name))?
.coerce_to_object(activation)?;
if object.is_of_type(param_type)? {
return Ok(object.into());
}
return Err(format!("Cannot coerce {:?} to an {:?}", self, type_name).into());
} }
} }
//type is unconstrained if let Some(static_class) = class.as_class() {
Ok(self.clone()) return Err(format!(
"Cannot coerce {:?} to an {:?}",
self,
static_class.read().name()
)
.into());
} else {
return Err(format!("Cannot coerce {:?} to {:?}", self, class).into());
}
} }
/// Determine if this value is any kind of number. /// Determine if this value is any kind of number.