v8/test/mjsunit/es6/computed-property-names-classes.js
Frank Emrich 300573aca8 [classes] Fix enumeration order bugs when accessors shadow dynamic prop.
AddToDictionaryTemplate in literal-objects.cc was missing several
cases when handling the overwriting between properties with statically
known and dynamically computed names. This led to wrong enumeration
orders in class prototypes created from class templates.

Bug: v8:11158
Change-Id: I7381b4680ec533bd307a6c32d75c8a66394869df
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2536464
Commit-Queue: Frank Emrich <emrich@google.com>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/master@{#71266}
2020-11-18 16:47:21 +00:00

738 lines
15 KiB
JavaScript

// Copyright 2014 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.
'use strict';
function ID(x) {
return x;
}
(function TestClassMethodString() {
class C {
a() { return 'A'}
['b']() { return 'B'; }
c() { return 'C'; }
[ID('d')]() { return 'D'; }
}
assertEquals('A', new C().a());
assertEquals('B', new C().b());
assertEquals('C', new C().c());
assertEquals('D', new C().d());
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['constructor', 'a', 'b', 'c', 'd'],
Object.getOwnPropertyNames(C.prototype));
})();
(function TestClassMethodNumber() {
class C {
a() { return 'A'; }
[1]() { return 'B'; }
c() { return 'C'; }
[ID(2)]() { return 'D'; }
}
assertEquals('A', new C().a());
assertEquals('B', new C()[1]());
assertEquals('C', new C().c());
assertEquals('D', new C()[2]());
// Array indexes first.
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['1', '2', 'constructor', 'a', 'c'],
Object.getOwnPropertyNames(C.prototype));
})();
(function TestClassMethodSymbol() {
var sym1 = Symbol();
var sym2 = Symbol();
class C {
a() { return 'A'; }
[sym1]() { return 'B'; }
c() { return 'C'; }
[ID(sym2)]() { return 'D'; }
}
assertEquals('A', new C().a());
assertEquals('B', new C()[sym1]());
assertEquals('C', new C().c());
assertEquals('D', new C()[sym2]());
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['constructor', 'a', 'c'],
Object.getOwnPropertyNames(C.prototype));
assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(C.prototype));
})();
(function TestStaticClassMethodString() {
class C {
static a() { return 'A'}
static ['b']() { return 'B'; }
static c() { return 'C'; }
static ['d']() { return 'D'; }
}
assertEquals('A', C.a());
assertEquals('B', C.b());
assertEquals('C', C.c());
assertEquals('D', C.d());
assertArrayEquals([], Object.keys(C));
// TODO(arv): It is not clear that we are adding the "standard" properties
// in the right order. As far as I can tell the spec adds them in alphabetical
// order.
assertArrayEquals(['length', 'prototype', 'a', 'b', 'c', 'd', 'name'],
Object.getOwnPropertyNames(C));
})();
(function TestStaticClassMethodNumber() {
class C {
static a() { return 'A'; }
static [1]() { return 'B'; }
static c() { return 'C'; }
static [2]() { return 'D'; }
}
assertEquals('A', C.a());
assertEquals('B', C[1]());
assertEquals('C', C.c());
assertEquals('D', C[2]());
// Array indexes first.
assertArrayEquals([], Object.keys(C));
assertArrayEquals(['1', '2', 'length', 'prototype', 'a', 'c', 'name'],
Object.getOwnPropertyNames(C));
})();
(function TestStaticClassMethodSymbol() {
var sym1 = Symbol();
var sym2 = Symbol();
class C {
static a() { return 'A'; }
static [sym1]() { return 'B'; }
static c() { return 'C'; }
static [sym2]() { return 'D'; }
}
assertEquals('A', C.a());
assertEquals('B', C[sym1]());
assertEquals('C', C.c());
assertEquals('D', C[sym2]());
assertArrayEquals([], Object.keys(C));
assertArrayEquals(['length', 'prototype', 'a', 'c', 'name'],
Object.getOwnPropertyNames(C));
assertArrayEquals([sym1, sym2], Object.getOwnPropertySymbols(C));
})();
function assertIteratorResult(value, done, result) {
assertEquals({ value: value, done: done}, result);
}
(function TestGeneratorComputedName() {
class C {
*['a']() {
yield 1;
yield 2;
}
}
var iter = new C().a();
assertIteratorResult(1, false, iter.next());
assertIteratorResult(2, false, iter.next());
assertIteratorResult(undefined, true, iter.next());
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['constructor', 'a'],
Object.getOwnPropertyNames(C.prototype));
})();
(function TestToNameSideEffects() {
var counter = 0;
var key1 = {
toString: function() {
assertEquals(0, counter++);
return 'b';
}
};
var key2 = {
toString: function() {
assertEquals(1, counter++);
return 'd';
}
};
class C {
a() { return 'A'; }
[key1]() { return 'B'; }
c() { return 'C'; }
[key2]() { return 'D'; }
}
assertEquals(2, counter);
assertEquals('A', new C().a());
assertEquals('B', new C().b());
assertEquals('C', new C().c());
assertEquals('D', new C().d());
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['constructor', 'a', 'b', 'c', 'd'],
Object.getOwnPropertyNames(C.prototype));
})();
(function TestToNameSideEffectsNumbers() {
var counter = 0;
var key1 = {
valueOf: function() {
assertEquals(0, counter++);
return 1;
},
toString: null
};
var key2 = {
valueOf: function() {
assertEquals(1, counter++);
return 2;
},
toString: null
};
class C {
a() { return 'A'; }
[key1]() { return 'B'; }
c() { return 'C'; }
[key2]() { return 'D'; }
}
assertEquals(2, counter);
assertEquals('A', new C().a());
assertEquals('B', new C()[1]());
assertEquals('C', new C().c());
assertEquals('D', new C()[2]());
// Array indexes first.
assertArrayEquals([], Object.keys(C.prototype));
assertArrayEquals(['1', '2', 'constructor', 'a', 'c'],
Object.getOwnPropertyNames(C.prototype));
})();
(function TestLength() {
class C {
static ['length']() {
return 42;
}
}
assertEquals(42, C.length());
class C1 {
static get ['length']() {
return 'A';
}
}
assertEquals('A', C1.length);
class C2 {
static get length() {
assertUnreachable();
}
static get ['length']() {
return 'B';
}
}
assertEquals('B', C2.length);
class C3 {
static get length() {
assertUnreachable();
}
static get ['length']() {
assertUnreachable();
}
static get ['length']() {
return 'C';
}
}
assertEquals('C', C3.length);
class C4 {
static get ['length']() {
assertUnreachable();
}
static get length() {
return 'D';
}
}
assertEquals('D', C4.length);
})();
(function TestGetter() {
class C {
get ['a']() {
return 'A';
}
}
assertEquals('A', new C().a);
class C2 {
get b() {
assertUnreachable();
}
get ['b']() {
return 'B';
}
}
assertEquals('B', new C2().b);
class C3 {
get c() {
assertUnreachable();
}
get ['c']() {
assertUnreachable();
}
get ['c']() {
return 'C';
}
}
assertEquals('C', new C3().c);
class C4 {
get ['d']() {
assertUnreachable();
}
get d() {
return 'D';
}
}
assertEquals('D', new C4().d);
})();
(function TestSetter() {
var calls = 0;
class C {
set ['a'](_) {
calls++;
}
}
new C().a = 'A';
assertEquals(1, calls);
calls = 0;
class C2 {
set b(_) {
assertUnreachable();
}
set ['b'](_) {
calls++;
}
}
new C2().b = 'B';
assertEquals(1, calls);
calls = 0;
class C3 {
set c(_) {
assertUnreachable()
}
set ['c'](_) {
assertUnreachable()
}
set ['c'](_) {
calls++
}
}
new C3().c = 'C';
assertEquals(1, calls);
calls = 0;
class C4 {
set ['d'](_) {
assertUnreachable()
}
set d(_) {
calls++
}
}
new C4().d = 'D';
assertEquals(1, calls);
})();
(function TestPrototype() {
assertThrows(function() {
class C {
static ['prototype']() {
return 1;
}
}
}, TypeError);
assertThrows(function() {
class C2 {
static get ['prototype']() {
return 2;
}
}
}, TypeError);
assertThrows(function() {
class C3 {
static set ['prototype'](x) {
assertEquals(3, x);
}
}
}, TypeError);
assertThrows(function() {
class C4 {
static *['prototype']() {
yield 1;
yield 2;
}
}
}, TypeError);
})();
(function TestPrototypeConcat() {
assertThrows(function() {
class C {
static ['pro' + 'tot' + 'ype']() {
return 1;
}
}
}, TypeError);
assertThrows(function() {
class C2 {
static get ['pro' + 'tot' + 'ype']() {
return 2;
}
}
}, TypeError);
assertThrows(function() {
class C3 {
static set ['pro' + 'tot' + 'ype'](x) {
assertEquals(3, x);
}
}
}, TypeError);
assertThrows(function() {
class C4 {
static *['pro' + 'tot' + 'ype']() {
yield 1;
yield 2;
}
}
}, TypeError);
})();
(function TestConstructor() {
// Normally a constructor property is not allowed.
class C {
['constructor']() {
return 1;
}
}
assertTrue(C !== C.prototype.constructor);
assertEquals(1, new C().constructor());
class C2 {
get ['constructor']() {
return 2;
}
}
assertEquals(2, new C2().constructor);
var calls = 0;
class C3 {
set ['constructor'](x) {
assertEquals(3, x);
calls++;
}
}
new C3().constructor = 3;
assertEquals(1, calls);
class C4 {
*['constructor']() {
yield 1;
yield 2;
}
}
var iter = new C4().constructor();
assertIteratorResult(1, false, iter.next());
assertIteratorResult(2, false, iter.next());
assertIteratorResult(undefined, true, iter.next());
})();
(function TestExceptionInName() {
function MyError() {};
function throwMyError() {
throw new MyError();
}
assertThrows(function() {
class C {
[throwMyError()]() {}
}
}, MyError);
assertThrows(function() {
class C {
get [throwMyError()]() { return 42; }
}
}, MyError);
assertThrows(function() {
class C {
set [throwMyError()](_) { }
}
}, MyError);
})();
(function TestTdzName() {
assertThrows(function() {
class C {
[C]() {}
}
}, ReferenceError);
assertThrows(function() {
class C {
get [C]() { return 42; }
}
}, ReferenceError);
assertThrows(function() {
class C {
set [C](_) { }
}
}, ReferenceError);
})();
// The following tests deal with computed and statically known properties
// of the same name overwriting each other.
//
// More concretely, we consider the cases where:
// The computed property appears ...
// - before an existing data property (case 1)
// - after an existing data property (case 2)
// - before an existing getter and setter pair (case 3)
// - before an existing getter xor setter (case 4)
// - after an existing getter and setter pair (case 5)
// - after an existing getter xor setter (case 6)
// - in between two existing accessors (case 7)
//
// For each of the 7 cases above, there exists A and B variants, indicating
// whether the computed property refers to an accessor (variant A) or a
// plain property (variant B).
// |expect_getter| and |expect_setter| must be undefined if and only if we are
// expecting |b| to be a data property.
function TestOverwritingHelper(clazz, expect_getter, expect_setter) {
var proto = clazz.prototype;
var desc = Object.getOwnPropertyDescriptor(proto, 'b');
if (desc.hasOwnProperty('value')) {
assertEquals(undefined, expect_getter);
assertEquals(undefined, expect_setter);
assertEquals('B', proto.b());
} else {
assertEquals("boolean", typeof expect_getter);
assertEquals("boolean", typeof expect_setter);
if (expect_getter) {
assertEquals('B', proto.b);
} else {
assertEquals(undefined, desc.getter);
}
assertEquals(expect_setter, desc.set !== undefined);
}
assertEquals('A', proto.a());
assertEquals('C', proto.c());
assertEquals('D', proto.d());
assertArrayEquals([], Object.keys(proto));
assertArrayEquals(['constructor', 'a', 'b', 'c', 'd'],
Object.getOwnPropertyNames(proto));
}
(function TestOverwriting1A() {
class C {
a() { return 'A'}
get [ID('b')]() { return 'Bx'; }
c() { return 'C'; }
b() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C);
})();
(function TestOverwriting1B() {
class C {
a() { return 'A'}
[ID('b')]() { return 'Bx'; }
c() { return 'C'; }
b() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C);
})();
(function TestOverwriting2A() {
class C {
a() { return 'A'}
b() { return 'Bx'; }
c() { return 'C'; }
get [ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, false);
})();
(function TestOverwriting2B() {
class C {
a() { return 'A'}
b() { return 'Bx'; }
c() { return 'C'; }
[ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C);
})();
(function TestOverwriting3A() {
class C {
a() { return 'A'}
get [ID('b')]() { return 'Bx'; }
c() { return 'C'; }
get b() { return 'B'; }
set b(foo) { this.x = foo; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, true);
})();
(function TestOverwriting3B() {
class C {
a() { return 'A'}
[ID('b')]() { return 'Bx'; }
c() { return 'C'; }
get b() { return 'B'; }
set b(foo) { this.x = foo; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, true);
})();
(function TestOverwriting4A() {
class C {
a() { return 'A'}
get [ID('b')]() { return 'Bx'; }
c() { return 'C'; }
get b() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, false);
})();
(function TestOverwriting4B() {
class C {
a() { return 'A'}
[ID('b')]() { return 'Bx'; }
c() { return 'C'; }
get b() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, false);
})();
(function TestOverwriting5A() {
class C {
a() { return 'A'}
get b() { return 'Bx'; }
set b(foo) { this.x = foo; }
c() { return 'C'; }
get [ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, true);
})();
(function TestOverwriting5B() {
class C {
a() { return 'A'}
get b() { return 'Bx'; }
set b(foo) { this.x = foo; }
c() { return 'C'; }
[ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C);
})();
(function TestOverwriting6A() {
class C {
a() { return 'A'}
set b(foo) { this.x = foo; }
c() { return 'C'; }
get [ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C, true, true);
})();
(function TestOverwriting6B() {
class C {
a() { return 'A'}
set b(foo) { this.x = foo; }
c() { return 'C'; }
[ID('b')]() { return 'B'; }
d() { return 'D'; }
}
TestOverwritingHelper(C);
})();
(function TestOverwriting7A() {
class C {
a() { return 'A'}
get b() { return 'Bx'; }
c() { return 'C'; }
get [ID('b')]() { return 'B'; }
d() { return 'D'; }
set b(foo) { this.x = foo; }
}
TestOverwritingHelper(C, true, true);
})();
(function TestOverwriting7B() {
class C {
a() { return 'A'}
set b(foo) { this.x = foo; }
c() { return 'C'; }
[ID('b')]() { return 'Bx'; }
d() { return 'D'; }
get b() { return 'B'; }
}
TestOverwritingHelper(C, true, false);
})();