| // 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); |
| })(); |
| |
| // Regression test for crbug.com/1139786 |
| (function HomeObjectProtoIsInt8ArrayAndReceiverIsSmi() { |
| class A extends Int8Array { |
| f() { |
| super.toString(); |
| } |
| }; |
| A.prototype.f.call(42); |
| })(); |