24fbcf8847
The actual fix is in LoadIC::ComputeHandler (checking lookup_start_object == holder instead of receiver == holder) + the LookupIterator changes for preserving lookup_start_object. The rest is renaming / refactoring. Reland: not relying on the prototype validity cell after all Previous version: https://chromium-review.googlesource.com/c/v8/v8/+/2414039 Bug: v8:9237, chromium:1127653 Change-Id: I1949442f8ddcecb776f0c5d2cf737cb75f80e313 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2428588 Reviewed-by: Igor Sheludko <ishell@chromium.org> Commit-Queue: Marja Hölttä <marja@chromium.org> Cr-Commit-Position: refs/heads/master@{#70112}
457 lines
11 KiB
JavaScript
457 lines
11 KiB
JavaScript
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Flags: --allow-natives-syntax --super-ic
|
|
|
|
function forceDictionaryMode(obj) {
|
|
for (let i = 0; i < 2000; ++i) {
|
|
obj["prop" + i] = "prop_value";
|
|
}
|
|
}
|
|
|
|
(function TestHomeObjectPrototypeNull() {
|
|
class A {}
|
|
|
|
class B extends A {
|
|
bar() {
|
|
return super.y;
|
|
}
|
|
};
|
|
|
|
let o = new B();
|
|
B.prototype.__proto__ = null;
|
|
assertThrows(() => { o.bar(); });
|
|
})();
|
|
|
|
(function TestMonomorphic() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {};
|
|
B.prototype.bar = "correct value";
|
|
|
|
class C extends B {};
|
|
|
|
class D extends C {
|
|
foo() { return super.bar; }
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
let o = new D();
|
|
o.bar = "wrong value: o.bar";
|
|
for (let i = 0; i < 100; ++i) {
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestMonomorphicWithGetter() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {
|
|
get bar() {
|
|
return this.test_value;
|
|
}
|
|
};
|
|
|
|
class C extends B {}
|
|
|
|
class D extends C {
|
|
foo() {
|
|
const b = super.bar;
|
|
return b;
|
|
}
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
let o = new D();
|
|
o.bar = "wrong value: o.bar";
|
|
o.test_value = "correct value";
|
|
for (let i = 0; i < 1000; ++i) {
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestPolymorphic() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {}
|
|
B.prototype.bar = "correct value";
|
|
|
|
class C extends B {}
|
|
|
|
class D extends C {
|
|
foo() { return super.bar; }
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
const o = new D();
|
|
|
|
// Create objects which will act as the "home object's prototype" later.
|
|
const prototypes = [{"a": 0}, {"b": 0}];
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
prototypes[i].__proto__ = B.prototype;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
D.prototype.__proto__ = prototypes[i % prototypes.length];
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestPolymorphicWithGetter() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {
|
|
get bar() {
|
|
return this.test_value;
|
|
}
|
|
};
|
|
|
|
class C extends B {}
|
|
|
|
class D extends C {
|
|
foo() { return super.bar; }
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
const o = new D();
|
|
o.test_value = "correct value";
|
|
|
|
// Create objects which will act as the "home object's prototype" later.
|
|
const prototypes = [{"a": 0}, {"b": 0}];
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
prototypes[i].__proto__ = B.prototype;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
D.prototype.__proto__ = prototypes[i % prototypes.length];
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestMegamorphic() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {}
|
|
B.prototype.bar = "correct value";
|
|
|
|
class C extends B {}
|
|
|
|
class D extends C {
|
|
foo() { return super.bar; }
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
const o = new D();
|
|
|
|
// Create objects which will act as the "home object's prototype" later.
|
|
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
|
|
{"f": 0}, {"g": 0}, {"e": 0}];
|
|
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
prototypes[i].__proto__ = B.prototype;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
D.prototype.__proto__ = prototypes[i % prototypes.length];
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestMegamorphicWithGetter() {
|
|
class A {}
|
|
A.prototype.bar = "wrong value: A.prototype.bar";
|
|
|
|
class B extends A {
|
|
get bar() {
|
|
return this.test_value;
|
|
}
|
|
};
|
|
|
|
class C extends B {}
|
|
|
|
class D extends C {
|
|
foo() { return super.bar;}
|
|
}
|
|
D.prototype.bar = "wrong value: D.prototype.bar";
|
|
|
|
const o = new D();
|
|
o.test_value = "correct value";
|
|
|
|
// Create objects which will act as the "home object's prototype" later.
|
|
const prototypes = [{"a": 0}, {"b": 0}, {"c": 0}, {"d": 0}, {"e": 0},
|
|
{"f": 0}, {"g": 0}, {"e": 0}];
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
prototypes[i].__proto__ = B.prototype;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
D.prototype.__proto__ = prototypes[i % prototypes.length];
|
|
const r = o.foo();
|
|
assertEquals("correct value", r);
|
|
}
|
|
})();
|
|
|
|
(function TestHolderHandledCorrectlyAfterOptimization() {
|
|
class A {
|
|
m() { return "m return value"; }
|
|
get boom() { return this.m; }
|
|
}
|
|
class B extends A { f() { return super.boom() } }
|
|
|
|
%PrepareFunctionForOptimization(B.prototype.f);
|
|
const r1 = new B().f();
|
|
assertEquals("m return value", r1);
|
|
const r2 = new B().f();
|
|
assertEquals("m return value", r2);
|
|
})();
|
|
|
|
(function TestHolderHandledCorrectlyAfterOptimization2() {
|
|
class A {
|
|
m() { return "m return value"; }
|
|
get boom() { return this.m; }
|
|
}
|
|
class Middle1 extends A {}
|
|
class Middle2 extends Middle1 {}
|
|
class B extends Middle2 { f() { return super.boom() } }
|
|
|
|
%PrepareFunctionForOptimization(B.prototype.f);
|
|
const r1 = new B().f();
|
|
assertEquals("m return value", r1);
|
|
const r2 = new B().f();
|
|
assertEquals("m return value", r2);
|
|
})();
|
|
|
|
(function TestStubCacheConfusion() {
|
|
// Regression test for using the wrong stub from the stub cache.
|
|
|
|
class A {};
|
|
A.prototype.foo = "foo from A.prototype";
|
|
|
|
class B extends A {
|
|
m() { return super.foo; }
|
|
}
|
|
|
|
// Create objects which will act as receivers for the method call. All of
|
|
// them will have a different map.
|
|
class C0 extends B { foo = "foo from C0"; };
|
|
class C1 extends B { foo = "foo from C1"; };
|
|
class C2 extends B { foo = "foo from C2"; };
|
|
class C3 extends B { foo = "foo from C3"; };
|
|
class C4 extends B { foo = "foo from C4"; };
|
|
class C5 extends B { foo = "foo from C5"; };
|
|
class C6 extends B { foo = "foo from C6"; };
|
|
class C7 extends B { foo = "foo from C7"; };
|
|
class C8 extends B { foo = "foo from C8"; };
|
|
class C9 extends B { foo = "foo from C9"; };
|
|
|
|
let receivers = [
|
|
new C0(), new C1(), new C2(), new C3(), new C4(), new C5(), new C6(), new C7(),
|
|
new C8(), new C9()
|
|
];
|
|
|
|
// Fill the stub cache with handlers which access "foo" from the receivers.
|
|
function getfoo(o) {
|
|
return o.foo;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
getfoo(receivers[i % receivers.length]);
|
|
}
|
|
|
|
// Create objects which will act as the "home object's prototype" later.
|
|
const prototypes = [{"a": "prop in prototypes[0]"},
|
|
{"b": "prop in prototypes[1]"},
|
|
{"c": "prop in prototypes[2]"},
|
|
{"d": "prop in prototypes[3]"},
|
|
{"e": "prop in prototypes[4]"},
|
|
{"f": "prop in prototypes[5]"},
|
|
{"g": "prop in prototypes[6]"},
|
|
{"h": "prop in prototypes[7]"},
|
|
{"i": "prop in prototypes[8]"},
|
|
{"j": "prop in prototypes[9]"}];
|
|
for (let i = 0; i < prototypes.length; ++i) {
|
|
prototypes[i].__proto__ = A.prototype;
|
|
}
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
// Make the property load for "super.foo" megamorphic in terms of the home
|
|
// object's prototype.
|
|
B.prototype.__proto__ = prototypes[i % prototypes.length];
|
|
const r = receivers[i % receivers.length].m();
|
|
// The bug was that we used the same handlers which were accessing "foo"
|
|
// from the receivers, instead of accessing "foo" from the home object's
|
|
// prototype.
|
|
assertEquals("foo from A.prototype", r);
|
|
}
|
|
})();
|
|
|
|
(function TestLengthConfusion() {
|
|
// Regression test for confusion between bound function length and array
|
|
// length.
|
|
class A {};
|
|
|
|
class B extends A {
|
|
m() {
|
|
return super.length;
|
|
}
|
|
}
|
|
|
|
// Create a "home object proto" object which is a bound function.
|
|
let home_object_proto = (function() {}).bind({});
|
|
forceDictionaryMode(home_object_proto);
|
|
B.prototype.__proto__ = home_object_proto;
|
|
|
|
assertEquals(0, home_object_proto.length);
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
const r = B.prototype.m.call([1, 2]);
|
|
// The bug was that here we retrieved the length of the receiver array
|
|
// instead of the home object's __proto__.
|
|
assertEquals(home_object_proto.length, r);
|
|
}
|
|
})();
|
|
|
|
(function TestSuperInsideArrowFunction() {
|
|
class A {};
|
|
A.prototype.foo = "correct value";
|
|
|
|
class B extends A {
|
|
m() {
|
|
const bar = () => {
|
|
return super.foo;
|
|
}
|
|
return bar();
|
|
}
|
|
|
|
n() {
|
|
const bar = () => {
|
|
return super.foo;
|
|
}
|
|
return bar;
|
|
}
|
|
};
|
|
|
|
assertEquals(A.prototype.foo, (new B).m());
|
|
assertEquals(A.prototype.foo, (new B).n()());
|
|
})();
|
|
|
|
// Regression test for a receiver vs lookup start object confusion.
|
|
(function TestProxyAsLookupStartObject1() {
|
|
class A {}
|
|
class B extends A {
|
|
bar() {
|
|
return super.foo;
|
|
}
|
|
}
|
|
|
|
const o = new B();
|
|
B.prototype.__proto__ = new Proxy({}, {});
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(undefined, o.bar());
|
|
}
|
|
})();
|
|
|
|
(function TestProxyAsLookupStartObject2() {
|
|
class A {}
|
|
class B extends A {
|
|
bar() {
|
|
return super.foo;
|
|
}
|
|
}
|
|
|
|
const o = new B();
|
|
forceDictionaryMode(o);
|
|
o.foo = "wrong value";
|
|
B.prototype.__proto__ = new Proxy({}, {});
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(undefined, o.bar());
|
|
}
|
|
})();
|
|
|
|
(function TestProxyAsLookupStartObject3() {
|
|
class A {}
|
|
class B extends A {
|
|
bar() {
|
|
return super.foo;
|
|
}
|
|
}
|
|
|
|
const o = new B();
|
|
B.prototype.__proto__ = new Proxy({}, {});
|
|
B.prototype.__proto__.foo = "correct value";
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(B.prototype.__proto__.foo, o.bar());
|
|
}
|
|
})();
|
|
|
|
(function TestDictionaryModeHomeObjectProto1() {
|
|
class A {}
|
|
forceDictionaryMode(A.prototype);
|
|
A.prototype.foo = "correct value";
|
|
class B extends A {
|
|
bar() {
|
|
return super.foo;
|
|
}
|
|
}
|
|
const o = new B();
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(A.prototype.foo, o.bar());
|
|
}
|
|
})();
|
|
|
|
(function TestDictionaryModeHomeObjectProto2() {
|
|
class A {}
|
|
A.prototype.foo = "correct value";
|
|
class B extends A {};
|
|
forceDictionaryMode(B.prototype);
|
|
class C extends B {
|
|
bar() {
|
|
return super.foo;
|
|
}
|
|
}
|
|
const o = new C();
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(A.prototype.foo, o.bar());
|
|
}
|
|
})();
|
|
|
|
(function TestHomeObjectProtoIsGlobalThis() {
|
|
class A {};
|
|
class B extends A {
|
|
bar() { return super.foo; }
|
|
}
|
|
B.prototype.__proto__ = globalThis;
|
|
|
|
const o = new B();
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(undefined, o.bar());
|
|
}
|
|
})();
|
|
|
|
// Regression test for (mis)using the prototype validity cell mechanism.
|
|
(function TestLoadFromDictionaryModePrototype() {
|
|
const obj1 = {};
|
|
const obj2 = {__proto__: obj1};
|
|
forceDictionaryMode(obj1);
|
|
|
|
for (let i = 0; i < 1000; ++i) {
|
|
assertEquals(undefined, obj1.x);
|
|
}
|
|
|
|
obj1.x = "added";
|
|
assertEquals("added", obj1.x);
|
|
})();
|