Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow supercalled methods to access properties of this correctly #499

Merged
merged 12 commits into from
Apr 26, 2020

Conversation

kmeisthax
Copy link
Member

Fixes #496. Also, properly implements AS2 virtual setters, which were broken but went unnoticed. All four types of supercalls (super-constructors, super-methods, super-getters, and super-setters) now work as expected.

Why it broke

The underlying super mechanism for AVM1 involves a special VM-provided object that pulls properties from the prototype that the currently-executing method was pulled from. This is difficult to correctly implement because it requires method calls to be treated differently from invoking a function with a particular value of this. The previous approach was to implement super as a proxy to this, but with one fewer prototype. This almost worked and was the approach that survived up until I decided to pare down super to the few operations that it actually supports. When you super-called a method, it would get the previous method's super as it's this. Making super more accurate to how Flash worked broke the ability for super-called methods to access their object properties.

How it got fixed

In AVM2, super is a set of opcodes, rather than an object. Because of this, I was more-or-less forced into avoiding the above hack and instead implementing super correctly. This PR constitutes a backport of PR #404's super handling to AVM1. When a method is called, the prototype that generated the method (called base_proto) is provided to call. If a method requests super, then a SuperObject is generated holding the base_proto. Furthermore, because super.method needs to bind the original this, a new trait method called call_method is provided to invoke a method on an object with correct this and super handling. All code, including builtin functions, that needs to invoke object methods should use call_method rather than resolving the property and calling it itself.

@Herschel
Copy link
Member

Herschel commented Apr 21, 2020

Thanks for the work and tests!

I don't think we need the constr backing variable -- there is an undocumented property called __constructor__ set by the Flash Player, that is a plain property set to be the "actual" constructor. I believe this is used to implement super, along with __proto__:

function SuperClass() {
	trace("SuperClass");
}
SuperClass.prototype.f = function() {
	trace("SuperClass f");
}

function SubClass() {
	super();
	trace("SubClass");
}
SubClass.prototype = new SuperClass();
SubClass.prototype.f = function() {
	trace("SubClass f");
	super.f();
}

// This is the link used for "super".
SubClass.prototype.__constructor__ = undefined;

// Because we nulled it out above, super constructor is not called here.
var subClass = new SubClass();

// This is the link used for "super.f".
subClass.__proto__.__proto__ = undefined;

// Because we nulled it out above, super.f() is undefined here.
subClass.f();

You can also try the ExtendedFurther.prototype.__constructor__ = null; in as2_super_via_manual_prototype test.

Another example:

function foo() {
	trace("FOO");
	super();
}

var o = {
	__proto__: {
		__constructor__: function() { trace("Fake"); }
	}
};

foo.apply(o);

kmeisthax and others added 11 commits April 25, 2020 13:25
…ions can still initialize the object they are a part of.
…e object from which a particular function was retrieved from.

A base prototype is only applicable in cases where a method is being called as a property on an object. Bare function calls, `apply`/`call` calls, and so on do not generate a base prototype.

We also add a convenience method, `call_method`, to all objects. This method automatically calculates the correct base prototype for a method call on an object, which is the only operation that should generate base prototypes.
…g copies of itself as `this`.

This allows supercalled functions to properly read and mutate the object they were called on.
…uper-methods, super-getters, and super-setters.
This is accomplished via two new `TObject` methods: `has_own_virtual` and `call_setter`. If an object does not contain it's own version of a property, it will first crawl the prototype chain to see if there is an overwritable virtual. If so, it will invoke that prototype's setter.

A bit of borrow finangling was required to do this; `super` now no longer caches it's proto and constr values and instead dynamically constructs them. This also means it can't be downcasted to `Executable` anymore.

With this commit, virtual setters and super-setters now work correctly.
@kmeisthax
Copy link
Member Author

I... really wish I had know about __constructor__ before I turned the entire project upside-down to add a TObject property for it. 😭

… property called `__constructor__` to store the constructor.

A previous version of this PR (whose history has been scrubbed, but go check 918d88a if you're curious) implemented a new `TObject` property which basically every line of code that dealt with object construction had to populate. It was terrible.
@kmeisthax
Copy link
Member Author

I've updated the PR to use __constructor__ rather than a TObject property. I've tried to roughly match it to how Flash does it: {} and (function () {}).prototype don't have it, but new (function () {}) does.

This passes all tests and clippy lints as far as I'm aware, but I may push again if it fails on CI or the newest Rust compiler update (currently installing) adds more failing lints.

@Herschel Herschel merged commit 03d4ed9 into ruffle-rs:master Apr 26, 2020
@Herschel
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

avm1 super doesn't correctly resolve this properties
3 participants