Calls to `super` inherently bind to itself.

This requires some subclassing nonsense to be able to smuggle a self-reference into `SuperObject`s. When successfully smuggled, all calls to `call` will be invoked with the `super` object as `this`. This allows constructor chaining to work.

Note that not all `Object` trait methods are implemented on `SuperObject`, so things like `delete this.x` in super constructors will randomly fail. This should be fixed.
This commit is contained in:
David Wendt 2019-11-29 22:23:08 -05:00 committed by Mike Welsh
parent 548f19ffbb
commit 854526923e
3 changed files with 31 additions and 4 deletions

View File

@ -237,6 +237,13 @@ impl<'gc> Executable<'gc> {
None
};
if let Some(super_object) = super_object {
super_object
.as_super_object()
.unwrap()
.bind_this(ac.gc_context, super_object);
}
let effective_ver = if avm.current_swf_version() > 5 {
af.swf_version()
} else {

View File

@ -227,6 +227,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Get the underlying script object, if it exists.
fn as_script_object(&self) -> Option<ScriptObject<'gc>>;
/// Get the underlying super object, if it exists.
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
None
}
/// Get the underlying display node for this object, if it exists.
fn as_display_object(&self) -> Option<DisplayObject<'gc>>;

View File

@ -25,6 +25,7 @@ pub struct SuperObjectData<'gc> {
child: Object<'gc>,
proto: Option<Object<'gc>>,
constr: Option<Object<'gc>>,
this: Option<Object<'gc>>,
}
impl<'gc> SuperObject<'gc> {
@ -33,10 +34,11 @@ impl<'gc> SuperObject<'gc> {
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Self, Error> {
let parent_proto = child.proto().and_then(|pr| pr.proto());
let parent_constr = if let Some(parent_proto) = parent_proto {
let child_proto = child.proto();
let parent_proto = child_proto.and_then(|pr| pr.proto());
let parent_constr = if let Some(child_proto) = child_proto {
Some(
parent_proto
child_proto
.get("constructor", avm, context)?
.resolve(avm, context)?
.as_object()?,
@ -51,9 +53,18 @@ impl<'gc> SuperObject<'gc> {
child,
proto: parent_proto,
constr: parent_constr,
this: None,
},
)))
}
/// Set `this` to a particular value.
///
/// This is intended to be called with a self-reference, so that future
/// invocations of `super()` can get a `this` value one level up the chain.
pub fn bind_this(&mut self, context: MutationContext<'gc, '_>, this: Object<'gc>) {
self.0.write(context).this = Some(this);
}
}
impl<'gc> TObject<'gc> for SuperObject<'gc> {
@ -86,7 +97,7 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(constr) = self.0.read().constr {
constr.call(avm, context, this, args)
constr.call(avm, context, self.0.read().this.unwrap_or(this), args)
} else {
Ok(Value::Undefined.into())
}
@ -224,6 +235,10 @@ impl<'gc> TObject<'gc> for SuperObject<'gc> {
None
}
fn as_super_object(&self) -> Option<SuperObject<'gc>> {
Some(*self)
}
fn as_display_object(&self) -> Option<DisplayObject<'gc>> {
self.0.read().child.as_display_object()
}