7fa12e2a4f
This patch implements https://github.com/tc39/proposal-class-fields/pull/269 and makes sure we always throw TypeError when there is invalid private name access in computed property keys. Before this patch, private name variables of private fields and methods are initialized together with computed property keys in the order they are declared. Accessing undefined private names in the computed property keys thus fail silently. After this patch, we initialize the private name variables of private fields before we initialize the computed property keys, so that invalid access to private fields in the computed keys can be checked in the IC. We now also initialize the brand early, so that invalid access to private methods or accessors in the computed keys throw TypeError during brand checks - and since these accesses are guarded by brand checks, we can create the private methods and accessors after the class is defined, and merge the home object setting with the creation of the closures. Bug: v8:8330, v8:9611 Change-Id: I01363f7befac6cf9dd28ec229b99a99102bcf012 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1846571 Commit-Queue: Joyee Cheung <joyee@igalia.com> Reviewed-by: Mythri Alle <mythria@chromium.org> Reviewed-by: Toon Verwaest <verwaest@chromium.org> Cr-Commit-Position: refs/heads/master@{#64225}
488 lines
6.9 KiB
JavaScript
488 lines
6.9 KiB
JavaScript
// Copyright 2018 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
|
|
|
|
|
|
"use strict";
|
|
|
|
{
|
|
class C {
|
|
#a;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
assertEquals(undefined, C.a);
|
|
|
|
let c = new C;
|
|
assertEquals(undefined, c.a);
|
|
assertEquals(undefined, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
assertEquals(undefined, C.a);
|
|
|
|
let c = new C;
|
|
assertEquals(undefined, c.a);
|
|
assertEquals(1, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
#b = this.#a;
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(1, c.getB());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
constructor() {
|
|
assertEquals(1, this.#a);
|
|
this.#a = 5;
|
|
}
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(5, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = this;
|
|
#b = () => this;
|
|
getA() { return this.#a; }
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let c1 = new C;
|
|
assertSame(c1, c1.getA());
|
|
assertSame(c1, c1.getB()());
|
|
let c2 = new C;
|
|
assertSame(c1, c1.getB().call(c2));
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = this;
|
|
#b = function() { return this; };
|
|
getA() { return this.#a; }
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let c1 = new C;
|
|
assertSame(c1, c1.getA());
|
|
assertSame(c1, c1.getB().call(c1));
|
|
let c2 = new C;
|
|
assertSame(c2, c1.getB().call(c2));
|
|
}
|
|
|
|
|
|
{
|
|
class C {
|
|
#a = function() { return 1 };
|
|
getA() {return this.#a;}
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals('#a', c.getA().name);
|
|
}
|
|
|
|
{
|
|
let d = function() { return new.target; }
|
|
class C {
|
|
#c = d;
|
|
getC() { return this.#c; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(undefined, c.getC()());
|
|
assertSame(new d, new (c.getC()));
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#b = new.target;
|
|
#c = () => new.target;
|
|
getB() { return this.#b; }
|
|
getC() { return this.#c; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(undefined, c.getB());
|
|
assertEquals(undefined, c.getC()());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
#b = () => this.#a;
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let c1 = new C;
|
|
assertSame(1, c1.getB()());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
getA(instance) { return instance.#a; }
|
|
}
|
|
|
|
class B { }
|
|
let c = new C;
|
|
assertEquals(undefined, c.a);
|
|
assertEquals(1, c.getA(c));
|
|
|
|
assertThrows(() => c.getA(new B), TypeError);
|
|
}
|
|
|
|
{
|
|
class A {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
class B extends A {}
|
|
let b = new B;
|
|
assertEquals(1, b.getA());
|
|
}
|
|
|
|
{
|
|
let prototypeLookup = false;
|
|
class A {
|
|
set a(val) {
|
|
prototypeLookup = true;
|
|
}
|
|
|
|
get a() { return undefined; }
|
|
}
|
|
|
|
class C extends A {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(1, c.getA());
|
|
assertEquals(false, prototypeLookup);
|
|
}
|
|
|
|
{
|
|
class A {
|
|
constructor() { this.a = 1; }
|
|
}
|
|
|
|
class B extends A {
|
|
#b = this.a;
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let b = new B;
|
|
assertEquals(1, b.getB());
|
|
}
|
|
|
|
{
|
|
class A {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
class B extends A {
|
|
#b = super.getA();
|
|
getB() { return this.#b; }
|
|
}
|
|
|
|
let b = new B;
|
|
assertEquals(1, b.getB());
|
|
}
|
|
|
|
{
|
|
class A {
|
|
#a = 1;
|
|
getA() { return this.#a;}
|
|
}
|
|
|
|
class B extends A {
|
|
#a = 2;
|
|
get_A() { return this.#a;}
|
|
}
|
|
|
|
let a = new A;
|
|
let b = new B;
|
|
assertEquals(1, a.getA());
|
|
assertEquals(1, b.getA());
|
|
assertEquals(2, b.get_A());
|
|
}
|
|
|
|
{
|
|
let foo = undefined;
|
|
class A {
|
|
#a = 1;
|
|
constructor() {
|
|
foo = this.#a;
|
|
}
|
|
}
|
|
|
|
let a = new A;
|
|
assertEquals(1, foo);
|
|
}
|
|
|
|
{
|
|
let foo = undefined;
|
|
class A extends class {} {
|
|
#a = 1;
|
|
constructor() {
|
|
super();
|
|
foo = this.#a;
|
|
}
|
|
}
|
|
|
|
let a = new A;
|
|
assertEquals(1, foo);
|
|
}
|
|
|
|
{
|
|
function makeClass() {
|
|
return class {
|
|
#a;
|
|
setA(val) { this.#a = val; }
|
|
getA() { return this.#a; }
|
|
}
|
|
}
|
|
|
|
let classA = makeClass();
|
|
let a = new classA;
|
|
let classB = makeClass();
|
|
let b = new classB;
|
|
|
|
assertEquals(undefined, a.getA());
|
|
assertEquals(undefined, b.getA());
|
|
|
|
a.setA(3);
|
|
assertEquals(3, a.getA());
|
|
assertEquals(undefined, b.getA());
|
|
|
|
b.setA(5);
|
|
assertEquals(3, a.getA());
|
|
assertEquals(5, b.getA());
|
|
|
|
assertThrows(() => a.getA.call(b), TypeError);
|
|
assertThrows(() => b.getA.call(a), TypeError);
|
|
}
|
|
|
|
{
|
|
let value = undefined;
|
|
|
|
new class {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
|
|
constructor() {
|
|
new class {
|
|
#a = 2;
|
|
constructor() {
|
|
value = this.#a;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
assertEquals(2, value);
|
|
}
|
|
|
|
{
|
|
class A {
|
|
#a = 1;
|
|
b = class {
|
|
getA() { return this.#a; }
|
|
get_A(val) { return val.#a; }
|
|
}
|
|
}
|
|
|
|
let a = new A();
|
|
let b = new a.b;
|
|
assertEquals(1, b.getA.call(a));
|
|
assertEquals(1, b.get_A(a));
|
|
}
|
|
|
|
{
|
|
class C {
|
|
b = this.#a;
|
|
#a = 1;
|
|
}
|
|
|
|
assertThrows(() => new C, TypeError);
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#b = this.#a;
|
|
#a = 1;
|
|
}
|
|
|
|
assertThrows(() => new C, TypeError);
|
|
}
|
|
|
|
{
|
|
let symbol = Symbol();
|
|
|
|
class C {
|
|
#a = 1;
|
|
[symbol] = 1;
|
|
getA() { return this.#a; }
|
|
setA(val) { this.#a = val; }
|
|
}
|
|
|
|
var p = new Proxy(new C, {
|
|
get: function(target, name) {
|
|
if (typeof(arg) === 'symbol') {
|
|
assertFalse(%SymbolIsPrivate(name));
|
|
}
|
|
return target[name];
|
|
}
|
|
});
|
|
|
|
assertThrows(() => p.getA(), TypeError);
|
|
assertThrows(() => p.setA(1), TypeError);
|
|
assertEquals(1, p[symbol]);
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#b = Object.freeze(this);
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(1, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
setA(another, val) { another.#a = val; }
|
|
getA(another) { return another.#a; }
|
|
}
|
|
|
|
let c = new C;
|
|
assertThrows(() => c.setA({}, 2), TypeError);
|
|
c.setA(c, 3);
|
|
assertEquals(3, c.getA(c));
|
|
}
|
|
|
|
{
|
|
class A {
|
|
constructor(arg) {
|
|
return arg;
|
|
}
|
|
}
|
|
|
|
class C extends A {
|
|
#x = 1;
|
|
|
|
constructor(arg) {
|
|
super(arg);
|
|
}
|
|
|
|
getX(arg) {
|
|
return arg.#x;
|
|
}
|
|
}
|
|
|
|
let leaker = new Proxy({}, {});
|
|
let c = new C(leaker);
|
|
assertEquals(1, C.prototype.getX(leaker));
|
|
assertSame(c, leaker);
|
|
|
|
c = new C();
|
|
assertThrows(() => new C(c), TypeError);
|
|
|
|
new C(1);
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
b;
|
|
getA() { return this.b().#a; }
|
|
}
|
|
|
|
let c = new C();
|
|
c.b = () => c;
|
|
assertEquals(1, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
b;
|
|
getA(arg) { return arg.b().#a; }
|
|
}
|
|
|
|
let c = new C();
|
|
c.b = () => c;
|
|
assertEquals(1, c.getA(c));
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
getA() { return eval('this.#a'); }
|
|
}
|
|
|
|
let c = new C;
|
|
assertEquals(1, c.getA());
|
|
}
|
|
|
|
{
|
|
var C;
|
|
eval('C = class {#a = 1;getA() { return eval(\'this.#a\'); }}');
|
|
|
|
let c = new C;
|
|
assertEquals(1, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
#a = 1;
|
|
getA() { return this.#a; }
|
|
setA() { eval('this.#a = 4'); }
|
|
}
|
|
let c = new C;
|
|
assertEquals(1, c.getA());
|
|
c.setA();
|
|
assertEquals(4, c.getA());
|
|
}
|
|
|
|
{
|
|
class C {
|
|
getA() { return eval('this.#a'); }
|
|
}
|
|
|
|
let c = new C;
|
|
assertThrows(() => c.getA(), SyntaxError);
|
|
}
|
|
|
|
{
|
|
assertThrows(() => {
|
|
class A {
|
|
[this.#a] = 1;
|
|
#a = 2;
|
|
}
|
|
}, TypeError);
|
|
}
|