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:
parent
713ab3e95c
commit
1c72167287
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
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)
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue