avm2: Fix get_super and set_super with normal methods

Doing `super.someNonGetter` gives you back a function object.
We were previously attempting to call normal methods as though
they were getters. Additionally, we were failing to properly
get the property from the superclass vtable.
This commit is contained in:
Aaron Hill 2023-04-11 21:05:32 -04:00
parent 30dc715c46
commit 33e9713279
7 changed files with 129 additions and 36 deletions

View File

@ -596,30 +596,49 @@ impl<'gc> ClassObject<'gc> {
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let property = self.instance_vtable().get_trait(multiname); let property = self.instance_vtable().get_trait(multiname);
if property.is_none() {
return Err(format!( match property {
"Attempted to supercall method {:?}, which does not exist", Some(
multiname.local_name() Property::Virtual {
)
.into());
}
if let Some(Property::Virtual {
get: Some(disp_id), .. get: Some(disp_id), ..
}) = property }
{ | Property::Method { disp_id },
) => {
// todo: handle errors // todo: handle errors
let ClassBoundMethod { let ClassBoundMethod {
class, class,
scope, scope,
method, method,
} = self.instance_vtable().get_full_method(disp_id).unwrap(); } = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee = let callee = FunctionObject::from_method(
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class)); activation,
method,
scope,
Some(receiver),
Some(class),
);
// We call getters, but return the actual function object for normal methods
if matches!(property, Some(Property::Virtual { .. })) {
callee.call(Some(receiver), &[], activation) callee.call(Some(receiver), &[], activation)
} else { } else {
Ok(callee.into())
}
}
Some(Property::Virtual { .. }) => Err(format!(
"Attempting to use get_super on non-getter property {:?}",
multiname
)
.into()),
Some(Property::Slot { .. } | Property::ConstSlot { .. }) => {
receiver.get_property(multiname, activation) receiver.get_property(multiname, activation)
} }
None => Err(format!(
"Attempted to supercall method {:?}, which does not exist",
multiname.local_name()
)
.into()),
}
} }
/// Supercall a setter defined in this class. /// Supercall a setter defined in this class.
@ -662,10 +681,11 @@ impl<'gc> ClassObject<'gc> {
) )
.into()); .into());
} }
if let Some(Property::Virtual {
match property {
Some(Property::Virtual {
set: Some(disp_id), .. set: Some(disp_id), ..
}) = property }) => {
{
// todo: handle errors // todo: handle errors
let ClassBoundMethod { let ClassBoundMethod {
class, class,
@ -676,10 +696,15 @@ impl<'gc> ClassObject<'gc> {
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class)); FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class));
callee.call(Some(receiver), &[value], activation)?; callee.call(Some(receiver), &[value], activation)?;
Ok(()) Ok(())
} else { }
receiver.set_property(multiname, value, activation) Some(Property::Slot { .. }) => {
receiver.set_property(multiname, value, activation)?;
Ok(())
}
_ => {
Err(format!("set_super on {receiver:?} {multiname:?} with {value:?} resolved to unexpected property {property:?}").into())
}
} }
} }

View File

@ -0,0 +1,36 @@
package {
public class Subclass extends Superclass {
public var subclassField:String = "Val";
public function Subclass() {
this.myMethod("First arg", true);
trace("this.myGetter: " + this.myGetter);
trace("super.myGetter: " + super.myGetter);
trace("super.superField: " + super.superField);
var obj = super;
trace("obj.myGetter: " + obj.myGetter);
this.mySetter = "setting_on_this";
super.mySetter = "setting_on_super";
}
public override function myMethod(arg1: String, arg2: Boolean) {
trace("In subclass myMethod: " + arg1 + " " + arg2);
super.myMethod("direct_arg", true);
super.myMethod.apply(this, ["apply_arg", true]);
}
public override function get myGetter():String {
trace("Calling subclass getter");
return "Value from subclass"
}
public override function set mySetter(val: String):void {
trace("Calling subclass getter with " + val);
}
}
}

View File

@ -0,0 +1,19 @@
package {
public class Superclass {
public var superField:Number = 20;
public function myMethod(arg1: String, arg2: Boolean) {
trace("In superclass myMethod: " + arg1 + " " + arg2);
}
public function get myGetter():String {
trace("Calling superclass getter");
return "Value from superclass"
}
public function set mySetter(val: String):void {
trace("Calling superclass getter with " + val);
}
}
}

View File

@ -0,0 +1,12 @@
In subclass myMethod: First arg true
In superclass myMethod: direct_arg true
In superclass myMethod: apply_arg true
Calling subclass getter
this.myGetter: Value from subclass
Calling superclass getter
super.myGetter: Value from superclass
super.superField: 20
Calling subclass getter
obj.myGetter: Value from subclass
Calling subclass getter with setting_on_this
Calling superclass getter with setting_on_super

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1