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

View File

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

View File

@ -29,6 +29,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// into account. /// into account.
fn get_property_local( fn get_property_local(
self, self,
reciever: Object<'gc>,
name: &QName, name: &QName,
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, '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. /// Retrieve a property by it's QName.
fn get_property( fn get_property(
self, self,
reciever: Object<'gc>,
name: &QName, name: &QName,
avm: &mut Avm2<'gc>, avm: &mut Avm2<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<ReturnValue<'gc>, Error> { ) -> Result<ReturnValue<'gc>, Error> {
if self.has_own_property(name) { 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() { 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()) 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. /// Set a property by it's QName.
fn set_property( fn set_property(
self, self,
reciever: Object<'gc>,
name: &QName, name: &QName,
value: Value<'gc>, value: Value<'gc>,
avm: &mut Avm2<'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.abc(),
type_entry.instance().super_name.clone(), type_entry.instance().super_name.clone(),
)?; )?;
let reciever: Object<'gc> = (*self).into();
let super_class: Result<Object<'gc>, Error> = self let super_class: Result<Object<'gc>, Error> = self
.get_property(&super_name, avm, context)? .get_property(reciever, &super_name, avm, context)?
.resolve(avm, context)? .resolve(avm, context)?
.as_object() .as_object()
.map_err(|_e| { .map_err(|_e| {

View File

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