avm2: Make `istype` use the constructor chain, not the prototype chain.

This pushes a few more ancillary changes:

 * `has_proto_in_chain` no longer checks interfaces (since it exists to serve `instanceof`, which does not respect them)
  * Interfaces no longer live on prototypes. They now live on class constructors.
This commit is contained in:
David Wendt 2021-06-02 22:07:03 -04:00
parent 713ab3e95c
commit 1c72167287
4 changed files with 80 additions and 63 deletions

View File

@ -226,7 +226,7 @@ impl<'gc> Avm2<'gc> {
.copied();
if let Some(object) = object {
if object.has_prototype_in_chain(on_class_proto, true)? {
if object.has_prototype_in_chain(on_class_proto)? {
Avm2::dispatch_event(context, event.clone(), object)?;
}
}

View File

@ -2286,7 +2286,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
});
let type_object = found?.coerce_to_object(self)?;
let is_instance_of = value.is_instance_of(self, type_object, true)?;
let is_instance_of = value.is_of_type(type_object)?;
self.context.avm2.push(is_instance_of);
Ok(FrameControl::Continue)
@ -2296,7 +2296,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let type_object = self.context.avm2.pop().coerce_to_object(self)?;
let value = self.context.avm2.pop().coerce_to_object(self)?;
let is_instance_of = value.is_instance_of(self, type_object, true)?;
let is_instance_of = value.is_of_type(type_object)?;
self.context.avm2.push(is_instance_of);
@ -2332,9 +2332,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return Err("TypeError: The right-hand side of operator must be a class.".into());
}
let is_instance_of = value.is_instance_of(self, class, true)?;
if is_instance_of {
if value.is_of_type(class)? {
self.context.avm2.push(value);
} else {
self.context.avm2.push(Value::Null);
@ -2351,9 +2349,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
return Err("TypeError: The right-hand side of operator must be a class.".into());
}
let is_instance_of = value.is_instance_of(self, class, true)?;
if is_instance_of {
if value.is_of_type(class)? {
self.context.avm2.push(value);
} else {
self.context.avm2.push(Value::Null);
@ -2367,7 +2363,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let value = self.context.avm2.pop().coerce_to_object(self).ok();
if let Some(value) = value {
let is_instance_of = value.is_instance_of(self, type_object, false)?;
let is_instance_of = value.is_instance_of(self, type_object)?;
self.context.avm2.push(is_instance_of);
} else {

View File

@ -919,43 +919,41 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// primitive value. Typically, this would be a number of some kind.
fn value_of(&self, mc: MutationContext<'gc, '_>) -> Result<Value<'gc>, Error>;
/// Enumerate all interfaces implemented by this object.
/// Enumerate all interfaces implemented by this constructor.
///
/// Non-constructors do not implement interfaces.
fn interfaces(&self) -> Vec<Object<'gc>>;
/// Set the interface list for this object.
/// Set the interface list for this constructor.
fn set_interfaces(&self, gc_context: MutationContext<'gc, '_>, iface_list: Vec<Object<'gc>>);
/// Determine if this object is an instance of a given type.
///
/// This uses the ES3 definition of instance, which walks the prototype
/// chain. For the ES4 definition of instance, use `is_of_type`, which uses
/// the constructor chain and accounts for interfaces.
///
/// The given object should be the constructor for the given type we are
/// checking against this object. Its prototype will be searched in the
/// prototype chain of this object. If `check_interfaces` is enabled, then
/// the interfaces listed on each prototype will also be checked.
/// checking against this object. Its prototype will be extracted and
/// searched in the prototype chain of this object.
#[allow(unused_mut)] //it's not unused
fn is_instance_of(
&self,
activation: &mut Activation<'_, 'gc, '_>,
mut constructor: Object<'gc>,
check_interfaces: bool,
) -> Result<bool, Error> {
let type_proto = constructor
.get_property(constructor, &QName::dynamic_name("prototype"), activation)?
.coerce_to_object(activation)?;
self.has_prototype_in_chain(type_proto, check_interfaces)
self.has_prototype_in_chain(type_proto)
}
/// Determine if this object has a given prototype in its prototype chain.
///
/// The given object should be the prototype we are checking against this
/// object. Its prototype will be searched in the
/// prototype chain of this object. If `check_interfaces` is enabled, then
/// the interfaces listed on each prototype will also be checked.
fn has_prototype_in_chain(
&self,
type_proto: Object<'gc>,
check_interfaces: bool,
) -> Result<bool, Error> {
/// The given object `type_proto` should be the prototype we are checking
/// against this object.
fn has_prototype_in_chain(&self, type_proto: Object<'gc>) -> Result<bool, Error> {
let mut my_proto = self.proto();
//TODO: Is it a verification error to do `obj instanceof bare_object`?
@ -964,15 +962,46 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(true);
}
if check_interfaces {
for interface in proto.interfaces() {
if Object::ptr_eq(interface, type_proto) {
return Ok(true);
}
my_proto = proto.proto()
}
Ok(false)
}
/// Determine if this object is an instance of a given type.
///
/// This uses the ES4 definition of instance, which walks the constructor
/// chain and accounts for interfaces. For the ES3 definition of instance,
/// use `is_instance_of`, which uses the prototype chain.
///
/// The given object should be the constructor for the given type we are
/// checking against this object.
#[allow(unused_mut)] //it's not unused
fn is_of_type(&self, mut constructor: Object<'gc>) -> Result<bool, Error> {
self.has_constr_in_chain(constructor)
}
/// Determine if this object has a given constructor in its constructor
/// chain.
///
/// The given object `constr` should be the type constructor we are
/// checking against this object. Interfaces, which are represented as type
/// constructors unrooted from `Object`, may also be used.
fn has_constr_in_chain(&self, type_constr: Object<'gc>) -> Result<bool, Error> {
let mut my_constr = self.as_constr();
while let Some(constr) = my_constr {
if Object::ptr_eq(constr, type_constr) {
return Ok(true);
}
for interface in constr.interfaces() {
if Object::ptr_eq(interface, type_constr) {
return Ok(true);
}
}
my_proto = proto.proto()
my_constr = constr.base_class_constr()
}
Ok(false)

View File

@ -77,37 +77,6 @@ impl<'gc> ClassObject<'gc> {
ScriptObject::bare_object(activation.context.gc_context)
};
let interface_names = class.read().interfaces().to_vec();
let mut interfaces = Vec::with_capacity(interface_names.len());
for interface_name in interface_names {
let interface = if let Some(scope) = scope {
scope
.write(activation.context.gc_context)
.resolve(&interface_name, activation)?
} else {
None
};
if interface.is_none() {
return Err(format!("Could not resolve interface {:?}", interface_name).into());
}
let mut interface = interface.unwrap().coerce_to_object(activation)?;
let iface_proto = interface
.get_property(
interface,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
interfaces.push(iface_proto);
}
if !interfaces.is_empty() {
class_proto.set_interfaces(activation.context.gc_context, interfaces);
}
let fn_proto = activation.avm2().prototypes().function;
let class_read = class.read();
@ -151,6 +120,29 @@ impl<'gc> ClassObject<'gc> {
constr.into(),
)?;
let interface_names = class.read().interfaces().to_vec();
let mut interfaces = Vec::with_capacity(interface_names.len());
for interface_name in interface_names {
let interface = if let Some(scope) = scope {
scope
.write(activation.context.gc_context)
.resolve(&interface_name, activation)?
} else {
None
};
if interface.is_none() {
return Err(format!("Could not resolve interface {:?}", interface_name).into());
}
let interface = interface.unwrap().coerce_to_object(activation)?;
interfaces.push(interface);
}
if !interfaces.is_empty() {
constr.set_interfaces(activation.context.gc_context, interfaces);
}
if !class_read.is_class_initialized() {
let class_initializer = class_read.class_init();
let class_init_fn =