Impure abstract getters on prototype chain (#2257)

Summary:
This fixes an issue where get on abstract top val didn't consider Receiver.

This also illustrates two common patterns of getters that are not pure. One is reading a mutable property known by Prepack and one is lazily initializing a shared mutable property.

I believe we'll want to continue supporting these patterns since they almost always work anyway. Note that supporting these don't add much negative consequence. We can still eliminate unused getters. Most of the time these patterns doesn't happen where the prototype is unknown or havoced.

Usually the getter is abstract because the Receiver itself is unknown or havoced (not its prototype). So havocing even more doesn't have any further negative downside.
Pull Request resolved: https://github.com/facebook/prepack/pull/2257

Differential Revision: D8862827

Pulled By: sebmarkbage

fbshipit-source-id: 7e6e98f8b85b6fceaa5131136cc6163d94f5d289
This commit is contained in:
Sebastian Markbage 2018-07-16 12:25:26 -07:00 committed by Facebook Github Bot
parent 7c6dddf236
commit 51e495a4e2
3 changed files with 71 additions and 2 deletions

View File

@ -498,7 +498,7 @@ export default class AbstractObjectValue extends AbstractValue {
if (this.values.isTop()) {
let generateAbstractGet = () => {
let ob = this;
let ob = Receiver;
if (this.kind === "explicit conversion to object") ob = this.args[0];
let type = Value;
if (P === "length" && Value.isTypeCompatibleWith(this.getType(), ArrayValue)) type = NumberValue;
@ -520,7 +520,7 @@ export default class AbstractObjectValue extends AbstractValue {
return generateAbstractGet();
} else if (this.$Realm.isInPureScope()) {
// This object might have leaked to a getter.
Havoc.value(this.$Realm, this);
Havoc.value(this.$Realm, Receiver);
// The getter might throw anything.
return this.$Realm.evaluateWithPossibleThrowCompletion(
generateAbstractGet,

View File

@ -0,0 +1,33 @@
var FooPrototype = global.__abstract
? __abstract("object", "({ get legacyCache() { return this._cache || (this._cache = {}); } })")
: {
get legacyCache() {
return this._cache || (this._cache = {});
},
};
function Foo() {}
Foo.prototype = FooPrototype;
function fn() {
var Bar = new Foo();
Object.defineProperty(Bar, "_cache", {
writable: true,
value: undefined,
});
Object.defineProperty(Bar, "cache", {
get() {
return this._cache || (this._cache = {});
},
});
return [Bar.legacyCache, Bar.cache];
}
global.fn = fn;
if (global.__optimize) {
__optimize(fn);
}
inspect = function() {
let [cache1, cache2] = fn();
return cache1 === cache2;
};

View File

@ -0,0 +1,36 @@
var FooPrototype = global.__abstract
? __abstract("object", "({ get name() { return this._name; } })")
: {
get name() {
return this._name;
},
};
function Foo(name) {
// Field initializer
Object.defineProperty(this, "_name", {
writable: true,
value: name,
});
}
Foo.prototype = FooPrototype;
function fn() {
var Bar = new Foo("Sebastian");
Object.defineProperty(Bar, "setName", {
value: function(name) {
return (this._name = name);
},
});
var name = Bar.name;
Bar.setName("Nikolai");
return name;
}
global.fn = fn;
if (global.__optimize) {
__optimize(fn);
}
inspect = function() {
return fn();
};