avm1: Store `__proto__` as a regular property

`__proto__` seems to behave much like a regular data property. So
simply remove the `prototype` field of `ScriptObject` in favor of
storing the prototype in the general properties hash map.
This commit is contained in:
relrelb 2021-06-23 19:58:52 +03:00 committed by Mike Welsh
parent 962bd41732
commit fd3f9f34de
11 changed files with 99 additions and 72 deletions

View File

@ -680,12 +680,16 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.base.delete(activation, name)
}
fn proto(&self) -> Value<'gc> {
self.base.proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.base.proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.base.set_proto(gc_context, prototype);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base.set_proto(activation, prototype)
}
fn define_value(

View File

@ -141,7 +141,7 @@ fn is_prototype_of<'gc>(
match args.get(0) {
Some(val) => {
let ob = val.coerce_to_object(activation);
Ok(this.is_prototype_of(ob).into())
Ok(this.is_prototype_of(activation, ob).into())
}
_ => Ok(false.into()),
}

View File

@ -114,10 +114,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
name: &str,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Value<'gc>, Error<'gc>> {
if name == "__proto__" {
return Ok(self.proto());
}
let this = (*self).into();
Ok(search_prototype(Value::Object(this), name, activation, this)?.0)
}
@ -142,11 +138,6 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(());
}
if name == "__proto__" {
self.set_proto(activation.context.gc_context, value);
return Ok(());
}
let this = (*self).into();
if !self.has_own_property(activation, name) {
// Before actually inserting a new property, we need to crawl the
@ -170,7 +161,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(());
}
proto = this_proto.proto();
proto = this_proto.proto(activation);
}
}
@ -273,14 +264,18 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// The proto is another object used to resolve methods across a class of
/// multiple objects. It should also be accessible as `__proto__` from
/// `get`.
fn proto(&self) -> Value<'gc>;
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc>;
/// Sets the `__proto__` of a given object.
///
/// The proto is another object used to resolve methods across a class of
/// multiple objects. It should also be accessible as `__proto__` in
/// `set`.
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>>;
/// Define a value on an object.
///
@ -415,7 +410,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
prototype: Object<'gc>,
) -> Result<bool, Error<'gc>> {
let mut proto_stack = vec![];
if let Value::Object(p) = self.proto() {
if let Value::Object(p) = self.proto(activation) {
proto_stack.push(p);
}
@ -424,7 +419,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return Ok(true);
}
if let Value::Object(p) = this_proto.proto() {
if let Value::Object(p) = this_proto.proto(activation) {
proto_stack.push(p);
}
@ -560,15 +555,19 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
fn as_ptr(&self) -> *const ObjectPtr;
/// Check if this object is in the prototype chain of the specified test object.
fn is_prototype_of(&self, other: Object<'gc>) -> bool {
let mut proto = other.proto();
fn is_prototype_of(
&self,
activation: &mut Activation<'_, 'gc, '_>,
other: Object<'gc>,
) -> bool {
let mut proto = other.proto(activation);
while let Value::Object(proto_ob) = proto {
if self.as_ptr() == proto_ob.as_ptr() {
return true;
}
proto = proto_ob.proto();
proto = proto_ob.proto(activation);
}
false
@ -635,7 +634,7 @@ pub fn search_prototype<'gc>(
return Ok((value?, Some(p)));
}
proto = p.proto();
proto = p.proto(activation);
depth += 1;
}

View File

@ -221,12 +221,16 @@ impl<'gc> TObject<'gc> for ArrayObject<'gc> {
.set_attributes(gc_context, name, set_attributes, clear_attributes)
}
fn proto(&self) -> Value<'gc> {
self.0.read().proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.0.read().proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.0.read().set_proto(gc_context, prototype);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.0.read().set_proto(activation, prototype)
}
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {

View File

@ -93,16 +93,16 @@ macro_rules! impl_custom_object {
self.0.read().$field.delete(activation, name)
}
fn proto(&self) -> crate::avm1::Value<'gc> {
self.0.read().$field.proto()
fn proto(&self, activation: &mut crate::avm1::Activation<'_, 'gc, '_>) -> crate::avm1::Value<'gc> {
self.0.read().$field.proto(activation)
}
fn set_proto(
&self,
gc_context: gc_arena::MutationContext<'gc, '_>,
activation: &mut crate::avm1::Activation<'_, 'gc, '_>,
prototype: crate::avm1::Value<'gc>,
) {
self.0.read().$field.set_proto(gc_context, prototype);
) -> Result<(), crate::avm1::Error<'gc>> {
self.0.read().$field.set_proto(activation, prototype)
}
fn define_value(

View File

@ -66,7 +66,6 @@ pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
#[derive(Collect)]
#[collect(no_drop)]
pub struct ScriptObjectData<'gc> {
prototype: Value<'gc>,
values: PropertyMap<Property<'gc>>,
interfaces: Vec<Object<'gc>>,
type_of: &'static str,
@ -76,7 +75,6 @@ pub struct ScriptObjectData<'gc> {
impl fmt::Debug for ScriptObjectData<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Object")
.field("prototype", &self.prototype)
.field("values", &self.values)
.field("watchers", &self.watchers)
.finish()
@ -85,16 +83,24 @@ impl fmt::Debug for ScriptObjectData<'_> {
impl<'gc> ScriptObject<'gc> {
pub fn object(gc_context: MutationContext<'gc, '_>, proto: Option<Object<'gc>>) -> Self {
Self(GcCell::allocate(
let object = Self(GcCell::allocate(
gc_context,
ScriptObjectData {
prototype: proto.map_or(Value::Undefined, Value::Object),
type_of: TYPE_OF_OBJECT,
values: PropertyMap::new(),
interfaces: vec![],
watchers: PropertyMap::new(),
},
))
));
if let Some(proto) = proto {
object.define_value(
gc_context,
"__proto__",
proto.into(),
Attribute::DONT_ENUM | Attribute::DONT_DELETE,
);
}
object
}
/// Constructs and allocates an empty but normal object in one go.
@ -429,18 +435,22 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
}
}
fn proto(&self) -> Value<'gc> {
self.0.read().prototype
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.get_data("__proto__", activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.0.write(gc_context).prototype = prototype;
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.set_data("__proto__", prototype, activation)
}
/// Checks if the object has a given named property.
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
self.has_own_property(activation, name)
|| if let Value::Object(proto) = self.proto() {
|| if let Value::Object(proto) = self.proto(activation) {
proto.has_property(activation, name)
} else {
false
@ -450,9 +460,6 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
/// Checks if the object has a given named property on itself (and not,
/// say, the object's prototype or superclass)
fn has_own_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {
if name == "__proto__" {
return true;
}
self.0
.read()
.values
@ -488,7 +495,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
/// Enumerate the object.
fn get_keys(&self, activation: &mut Activation<'_, 'gc, '_>) -> Vec<String> {
let proto_keys = if let Value::Object(proto) = self.proto() {
let proto_keys = if let Value::Object(proto) = self.proto(activation) {
proto.get_keys(activation)
} else {
Vec::new()

View File

@ -269,12 +269,16 @@ impl<'gc> TObject<'gc> for StageObject<'gc> {
self.0.read().base.delete(activation, name)
}
fn proto(&self) -> Value<'gc> {
self.0.read().base.proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.0.read().base.proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.0.read().base.set_proto(gc_context, prototype);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.0.read().base.set_proto(activation, prototype)
}
fn define_value(

View File

@ -55,17 +55,12 @@ impl<'gc> SuperObject<'gc> {
)))
}
/// Retrieve the prototype that `super` should be pulling from.
fn super_proto(self) -> Value<'gc> {
self.0.read().base_proto.proto()
}
/// Retrieve the constructor associated with the super proto.
fn super_constr(
self,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Option<Object<'gc>>, Error<'gc>> {
if let Value::Object(super_proto) = self.super_proto() {
if let Value::Object(super_proto) = self.proto(activation) {
Ok(Some(
super_proto
.get("__constructor__", activation)?
@ -107,7 +102,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(constr) = self.super_constr(activation)? {
let super_proto = match self.super_proto() {
let super_proto = match self.proto(activation) {
Value::Object(o) => Some(o),
_ => None,
};
@ -124,7 +119,8 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Value<'gc>, Error<'gc>> {
let child = self.0.read().child;
let (method, base_proto) = search_prototype(self.super_proto(), name, activation, child)?;
let (method, base_proto) =
search_prototype(self.proto(activation), name, activation, child)?;
if method.is_primitive() {
avm_warn!(activation, "Super method {} is not callable", name);
@ -147,7 +143,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
if let Value::Object(proto) = self.proto() {
if let Value::Object(proto) = self.proto(activation) {
proto.create_bare_object(activation, this)
} else {
// TODO: What happens when you `new super` but there's no
@ -161,14 +157,19 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
false
}
fn proto(&self) -> Value<'gc> {
self.super_proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.0.read().base_proto.proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
if let Value::Object(prototype) = prototype {
self.0.write(gc_context).base_proto = prototype;
self.0.write(activation.context.gc_context).base_proto = prototype;
}
Ok(())
}
fn define_value(

View File

@ -178,12 +178,16 @@ impl<'gc> TObject<'gc> for XmlAttributesObject<'gc> {
.set_attributes(gc_context, name, set_attributes, clear_attributes)
}
fn proto(&self) -> Value<'gc> {
self.base().proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.base().proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.base().set_proto(gc_context, prototype);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base().set_proto(activation, prototype)
}
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {

View File

@ -179,12 +179,16 @@ impl<'gc> TObject<'gc> for XmlIdMapObject<'gc> {
.set_attributes(gc_context, name, set_attributes, clear_attributes)
}
fn proto(&self) -> Value<'gc> {
self.base().proto()
fn proto(&self, activation: &mut Activation<'_, 'gc, '_>) -> Value<'gc> {
self.base().proto(activation)
}
fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Value<'gc>) {
self.base().set_proto(gc_context, prototype);
fn set_proto(
&self,
activation: &mut Activation<'_, 'gc, '_>,
prototype: Value<'gc>,
) -> Result<(), Error<'gc>> {
self.base().set_proto(activation, prototype)
}
fn has_property(&self, activation: &mut Activation<'_, 'gc, '_>, name: &str) -> bool {

View File

@ -1309,7 +1309,7 @@ impl Player {
);
if let Ok(prototype) = constructor.get("prototype", &mut activation) {
if let Value::Object(object) = actions.clip.object() {
object.set_proto(activation.context.gc_context, prototype);
object.set_proto(&mut activation, prototype).unwrap(); // TODO: What happens on error?
for event in events {
let _ = activation.run_child_frame_for_action(
"[Actions]",