Implement `getsuper` and `setsuper`.

This required the reintroduction of dedicated reciever parameters to `Object.get_property_local` and `Object.set_property`, which I had removed from the AVM1 code I copied it from. It turns out being able to change the reciever was actually necessary in order to make super set/get work.
This commit is contained in:
David Wendt 2020-02-27 23:38:56 -05:00
parent 785832b7f3
commit 665d7a4342
5 changed files with 105 additions and 22 deletions

View File

@ -457,6 +457,8 @@ impl<'gc> Avm2<'gc> {
Op::SetProperty { index } => self.op_set_property(context, index),
Op::InitProperty { index } => self.op_init_property(context, index),
Op::DeleteProperty { index } => self.op_delete_property(context, index),
Op::GetSuper { index } => self.op_get_super(context, index),
Op::SetSuper { index } => self.op_set_super(context, index),
Op::PushScope => self.op_push_scope(context),
Op::PushWith => self.op_push_with(context),
Op::PopScope => self.op_pop_scope(context),
@ -637,7 +639,7 @@ impl<'gc> Avm2<'gc> {
let base_proto = receiver.get_base_proto(&name);
let function = base_proto
.unwrap_or(receiver)
.get_property_local(&name, self, context)?
.get_property_local(receiver, &name, self, context)?
.resolve(self, context)?
.as_object()?;
@ -661,7 +663,7 @@ impl<'gc> Avm2<'gc> {
.resolve_multiname(&multiname)
.ok_or_else(|| format!("Could not find method {:?}", multiname.local_name()).into());
let function = receiver
.get_property(&name?, self, context)?
.get_property(receiver, &name?, self, context)?
.resolve(self, context)?
.as_object()?;
@ -686,7 +688,7 @@ impl<'gc> Avm2<'gc> {
let base_proto = receiver.get_base_proto(&name);
let function = base_proto
.unwrap_or(receiver)
.get_property_local(&name, self, context)?
.get_property_local(receiver, &name, self, context)?
.resolve(self, context)?
.as_object()?;
@ -747,7 +749,7 @@ impl<'gc> Avm2<'gc> {
let base_proto = base_proto?;
let function = base_proto
.get_property(&name?, self, context)?
.get_property(receiver, &name?, self, context)?
.resolve(self, context)?
.as_object()?;
@ -784,7 +786,7 @@ impl<'gc> Avm2<'gc> {
let base_proto = base_proto?;
let function = base_proto
.get_property(&name?, self, context)?
.get_property(receiver, &name?, self, context)?
.resolve(self, context)?
.as_object()?;
@ -818,7 +820,7 @@ impl<'gc> Avm2<'gc> {
});
let value = object
.get_property(&name?, self, context)?
.get_property(object, &name?, self, context)?
.resolve(self, context)?;
self.push(value);
@ -835,7 +837,7 @@ impl<'gc> Avm2<'gc> {
let object = self.pop().as_object()?;
if let Some(name) = object.resolve_multiname(&multiname) {
object.set_property(&name, value, self, context)
object.set_property(object, &name, value, self, context)
} else {
//TODO: Non-dynamic objects should fail
//TODO: This should only work if the public namespace is present
@ -843,7 +845,7 @@ impl<'gc> Avm2<'gc> {
.local_name()
.ok_or_else(|| "Cannot set property using any name".into());
let name = QName::dynamic_name(local_name?);
object.set_property(&name, value, self, context)
object.set_property(object, &name, value, self, context)
}
}
@ -886,6 +888,72 @@ impl<'gc> Avm2<'gc> {
Ok(())
}
fn op_get_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
let object = self.pop().as_object()?;
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
let name: Result<QName, Error> =
base_proto.resolve_multiname(&multiname).ok_or_else(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
let value = base_proto
.get_property(object, &name?, self, context)?
.resolve(self, context)?;
self.push(value);
Ok(())
}
fn op_set_super(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
index: Index<AbcMultiname>,
) -> Result<(), Error> {
let multiname = self.pool_multiname(index)?;
let object = self.pop().as_object()?;
let base_proto: Result<Object<'gc>, Error> = self
.current_stack_frame()
.unwrap()
.read()
.base_proto()
.and_then(|p| p.proto())
.ok_or_else(|| "Attempted to get property on non-existent super object".into());
let base_proto = base_proto?;
let name: Result<QName, Error> =
base_proto.resolve_multiname(&multiname).ok_or_else(|| {
format!(
"Could not resolve {:?} as super property",
multiname.local_name()
)
.into()
});
let value = self.pop();
base_proto.set_property(object, &name?, value, self, context)?;
Ok(())
}
fn op_push_scope(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let object = self.pop().as_object()?;
let activation = self.current_stack_frame().unwrap();
@ -1071,6 +1139,7 @@ impl<'gc> Avm2<'gc> {
let proto = ctor
.get_property(
ctor,
&QName::new(Namespace::public_namespace(), "prototype"),
self,
context,
@ -1102,11 +1171,12 @@ impl<'gc> Avm2<'gc> {
format!("Could not resolve property {:?}", multiname.local_name()).into()
});
let ctor = source
.get_property(&ctor_name?, self, context)?
.get_property(source, &ctor_name?, self, context)?
.resolve(self, context)?
.as_object()?;
let proto = ctor
.get_property(
ctor,
&QName::new(Namespace::public_namespace(), "prototype"),
self,
context,
@ -1145,7 +1215,7 @@ impl<'gc> Avm2<'gc> {
let base_proto = base_proto?;
let function = base_proto
.get_property_local(&name, self, context)?
.get_property_local(receiver, &name, self, context)?
.resolve(self, context)?
.as_object()?;
@ -1174,6 +1244,7 @@ impl<'gc> Avm2<'gc> {
let name = self.pop();
object.set_property(
object,
&QName::new(Namespace::public_namespace(), name.as_string()?),
value,
self,

View File

@ -268,6 +268,7 @@ impl<'gc> FunctionObject<'gc> {
) -> Result<(Object<'gc>, Object<'gc>), Error> {
let super_proto: Result<Object<'gc>, Error> = base_class
.get_property(
base_class,
&QName::new(Namespace::public_namespace(), "prototype"),
avm,
context,
@ -432,6 +433,7 @@ impl<'gc> FunctionObject<'gc> {
impl<'gc> TObject<'gc> for FunctionObject<'gc> {
fn get_property_local(
self,
reciever: Object<'gc>,
name: &QName,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
@ -439,11 +441,12 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0
.read()
.base
.get_property_local(name, avm, context, self.into())
.get_property_local(reciever, name, avm, context)
}
fn set_property(
self,
reciever: Object<'gc>,
name: &QName,
value: Value<'gc>,
avm: &mut Avm2<'gc>,
@ -452,7 +455,7 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
self.0
.write(context.gc_context)
.base
.set_property(name, value, avm, context, self.into())
.set_property(reciever, name, value, avm, context)
}
fn init_property(

View File

@ -29,6 +29,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// into account.
fn get_property_local(
self,
reciever: Object<'gc>,
name: &QName,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
@ -37,16 +38,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Retrieve a property by it's QName.
fn get_property(
self,
reciever: Object<'gc>,
name: &QName,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> {
if self.has_own_property(name) {
return self.get_property_local(name, avm, context);
return self.get_property_local(reciever, name, avm, context);
}
if let Some(proto) = self.proto() {
return proto.get_property(name, avm, context);
return proto.get_property(reciever, name, avm, context);
}
Ok(Value::Undefined.into())
@ -68,6 +70,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Set a property by it's QName.
fn set_property(
self,
reciever: Object<'gc>,
name: &QName,
value: Value<'gc>,
avm: &mut Avm2<'gc>,
@ -263,8 +266,9 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
&type_entry.abc(),
type_entry.instance().super_name.clone(),
)?;
let reciever: Object<'gc> = (*self).into();
let super_class: Result<Object<'gc>, Error> = self
.get_property(&super_name, avm, context)?
.get_property(reciever, &super_name, avm, context)?
.resolve(avm, context)?
.as_object()
.map_err(|_e| {

View File

@ -128,7 +128,10 @@ impl<'gc> Scope<'gc> {
) -> Option<Result<ReturnValue<'gc>, Error>> {
if let Some(qname) = self.locals().resolve_multiname(name) {
if self.locals().has_property(&qname) {
return Some(self.locals().get_property(&qname, avm, context));
return Some(
self.locals()
.get_property(self.values, &qname, avm, context),
);
}
}

View File

@ -37,17 +37,19 @@ pub struct ScriptObjectData<'gc> {
impl<'gc> TObject<'gc> for ScriptObject<'gc> {
fn get_property_local(
self,
reciever: Object<'gc>,
name: &QName,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> {
self.0
.read()
.get_property_local(name, avm, context, self.into())
.get_property_local(reciever, name, avm, context)
}
fn set_property(
self,
reciever: Object<'gc>,
name: &QName,
value: Value<'gc>,
avm: &mut Avm2<'gc>,
@ -55,7 +57,7 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
) -> Result<(), Error> {
self.0
.write(context.gc_context)
.set_property(name, value, avm, context, self.into())
.set_property(reciever, name, value, avm, context)
}
fn init_property(
@ -213,15 +215,15 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn get_property_local(
&self,
reciever: Object<'gc>,
name: &QName,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
) -> Result<ReturnValue<'gc>, Error> {
let prop = self.values.get(name);
if let Some(prop) = prop {
prop.get(avm, context, this)
prop.get(avm, context, reciever)
} else {
Ok(Value::Undefined.into())
}
@ -229,17 +231,17 @@ impl<'gc> ScriptObjectData<'gc> {
pub fn set_property(
&mut self,
reciever: Object<'gc>,
name: &QName,
value: Value<'gc>,
avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
) -> Result<(), Error> {
if let Some(prop) = self.values.get_mut(name) {
if let Some(slot_id) = prop.slot_id() {
self.set_slot(slot_id, value, context.gc_context)?;
} else {
prop.set(avm, context, this, value)?;
prop.set(avm, context, reciever, value)?;
}
} else {
//TODO: Not all classes are dynamic like this