4e8c62819a
This patch refactors the declaration and allocation of the class variable, and implements static private methods: - The class variable is declared in the class scope with an explicit reference through class_scope->class_variable(). Anonymous classes whose class variable may be accessed transitively through static private method access use the dot string as the class name. Whether the class variable is allocated depending on whether it is used. Other references of the class variable in the ClassLiteral AST node and the ClassInfo structure are removed in favor of the reference through the class scope. - Previously the class variable was always (stack- or context-) allocated if the class is named. Now if the class variable is only referenced by name, it's stack allocated. If it's used transitively by access to static private methods, or may be used through eval, it's context allocated. Therefore we now use 1 less context slots in the class context if it's a named class without anyone referencing it by name in inner scopes. - Explicit access to static private methods or potential access to static private methods through eval results in forced context allocation of the class variables. In those cases, we save its index in context locals in the ScopeInfo and deserialize it later, so that we can check that the receiver of static private methods is the class constructor at run time. This flag is recorded as HasSavedClassVariableIndexField in the scope info. - Classes that need the class variable to be saved due to access to static private methods now save a ShouldSaveClassVariableIndexField in the preparse data so that the bits on the variables can be updated during a reparse. In the case of anonymous classes that need the class variables to be saved, we also re-declare the class variable after the reparse since the inner functions are skipped and we need to rely on the preparse data flags to remember declaring it. Design doc: https://docs.google.com/document/d/1rgGRw5RdzaRrM-GrIMhsn-DLULtADV2dmIdh_iIZxlc/edit Bug: v8:8330 Change-Id: Idd07803f47614e97ad202de3b7faa9f71105eac5 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1781011 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@{#64219}
249 lines
6.3 KiB
JavaScript
249 lines
6.3 KiB
JavaScript
// Copyright 2019 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: --harmony-private-methods
|
|
|
|
"use strict";
|
|
|
|
// Static private methods
|
|
{
|
|
let store = 1;
|
|
class C {
|
|
static #a() { return store; }
|
|
static a() { return this.#a(); }
|
|
}
|
|
assertEquals(C.a(), store);
|
|
assertThrows(() => C.a.call(new C), TypeError);
|
|
}
|
|
|
|
// Complementary static private accessors.
|
|
{
|
|
let store = 1;
|
|
class C {
|
|
static get #a() { return store; }
|
|
static set #a(val) { store = val; }
|
|
static incA() { this.#a++; }
|
|
static getA() { return this.#a; }
|
|
static setA(val) { this.#a = val; }
|
|
}
|
|
assertEquals(C.getA(), 1);
|
|
C.incA();
|
|
assertEquals(store, 2);
|
|
C.setA(3);
|
|
assertEquals(store, 3);
|
|
|
|
assertThrows(() => C.incA.call(new C), TypeError);
|
|
assertThrows(() => C.getA.call(new C), TypeError);
|
|
assertThrows(() => C.setA.call(new C), TypeError);
|
|
|
|
assertThrows(() => { const incA = C.incA; incA(); }, TypeError);
|
|
assertThrows(() => { const getA = C.getA; getA(); }, TypeError);
|
|
assertThrows(() => { const setA = C.setA; setA(); }, TypeError);
|
|
}
|
|
|
|
// Static private methods accessed explicitly in an anonymous nested class.
|
|
{
|
|
class Outer {
|
|
#a() { return 'Outer'; }
|
|
a() { return this.#a(); }
|
|
test() {
|
|
return class {
|
|
static #a() { return 'Inner'; }
|
|
static a() { return this.#a(); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const obj = new Outer;
|
|
const C = obj.test();
|
|
assertEquals(C.a(), 'Inner');
|
|
assertThrows(() => obj.a.call(C), TypeError);
|
|
assertThrows(() => obj.a.call(new C), TypeError);
|
|
}
|
|
|
|
// Static private methods accessed explicitly in a named nested class.
|
|
{
|
|
class Outer {
|
|
#a() { return 'Outer'; }
|
|
a() { return this.#a(); }
|
|
test() {
|
|
return class Inner {
|
|
static #a() { return 'Inner'; }
|
|
static a() { return this.#a(); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const obj = new Outer;
|
|
const C = obj.test();
|
|
assertEquals(C.a(), 'Inner');
|
|
assertThrows(() => obj.a.call(C), TypeError);
|
|
assertThrows(() => obj.a.call(new C), TypeError);
|
|
}
|
|
|
|
// Static private methods accessed through eval in an anonymous nested class.
|
|
{
|
|
class Outer {
|
|
#a() { return 'Outer'; }
|
|
a() { return this.#a(); }
|
|
test() {
|
|
return class {
|
|
static #a() { return 'Inner'; }
|
|
static a(str) { return eval(str); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const obj = new Outer;
|
|
const C = obj.test();
|
|
assertEquals(C.a('this.#a()'), 'Inner');
|
|
assertThrows(() => C.a('Outer.#a()'), TypeError);
|
|
}
|
|
|
|
// Static private methods accessed through eval in a named nested class.
|
|
{
|
|
class Outer {
|
|
#a() { return 'Outer'; }
|
|
a() { return this.#a(); }
|
|
test() {
|
|
return class Inner {
|
|
static #a() { return 'Inner'; }
|
|
static a(str) { return eval(str); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const obj = new Outer;
|
|
const C = obj.test();
|
|
assertEquals(C.a('this.#a()'), 'Inner');
|
|
assertEquals(C.a('Inner.#a()'), 'Inner');
|
|
assertThrows(() => C.a('Outer.#a()'), TypeError);
|
|
assertThrows(() => C.run('(new Outer).#a()'), TypeError);
|
|
}
|
|
|
|
// Static private methods in the outer class accessed through eval
|
|
// in a named nested class.
|
|
{
|
|
class Outer {
|
|
static #a() { return 'Outer'; }
|
|
static test() {
|
|
return class Inner {
|
|
static run(str) { return eval(str); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const C = Outer.test();
|
|
assertEquals(C.run('Outer.#a()'), 'Outer');
|
|
assertThrows(() => C.run('this.#a()'), TypeError);
|
|
assertThrows(() => C.run('Inner.#a()'), TypeError);
|
|
assertThrows(() => C.run('(new Outer).#a()'), TypeError);
|
|
}
|
|
|
|
// Static private methods in the outer class accessed explicitly
|
|
// in a named nested class.
|
|
{
|
|
class Outer {
|
|
static #a() { return 'Outer'; }
|
|
static test() {
|
|
return class Inner {
|
|
static getA(klass) { return klass.#a(); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const C = Outer.test();
|
|
assertEquals(C.getA(Outer), 'Outer');
|
|
assertThrows(() => C.getA.call(C), TypeError);
|
|
assertThrows(() => C.getA.call(new Outer), TypeError);
|
|
}
|
|
|
|
// Static private methods in the outer class accessed explicitly
|
|
// in an anonymous nested class.
|
|
{
|
|
class Outer {
|
|
static #a() { return 'Outer'; }
|
|
static test() {
|
|
return class {
|
|
static getA(klass) { return klass.#a(); }
|
|
};
|
|
}
|
|
}
|
|
|
|
const C = Outer.test();
|
|
assertEquals(C.getA(Outer), 'Outer');
|
|
assertThrows(() => C.getA.call(C), TypeError);
|
|
assertThrows(() => C.getA.call(new Outer), TypeError);
|
|
}
|
|
|
|
// Super property access in static private methods
|
|
{
|
|
class A {
|
|
static a = 1;
|
|
}
|
|
|
|
class B extends A {
|
|
static #a() { return super.a; }
|
|
static getA() { return this.#a(); }
|
|
}
|
|
|
|
assertEquals(B.getA(), 1);
|
|
}
|
|
|
|
// Invalid super property access in static private methods
|
|
{
|
|
class A {
|
|
static #a() { return 1; }
|
|
static getA() { return this.#a(); }
|
|
}
|
|
|
|
class B extends A {
|
|
static getA() { return super.getA(); }
|
|
}
|
|
|
|
assertThrows(() => B.getA(), TypeError);
|
|
}
|
|
|
|
// Static private methods accessed in eval.
|
|
{
|
|
class C {
|
|
static #m(v) { return v; }
|
|
static test(str) {
|
|
return eval(str);
|
|
}
|
|
}
|
|
|
|
assertEquals(C.test('this.#m(1)'), 1);
|
|
}
|
|
|
|
// Test that the receiver is checked during run time.
|
|
{
|
|
const C = class {
|
|
static #a() { }
|
|
static test(klass) { return klass.#a; }
|
|
};
|
|
const test = C.test;
|
|
assertThrows(test, TypeError);
|
|
}
|
|
|
|
// Duplicate static private accessors and methods.
|
|
{
|
|
assertThrows('class C { static get #a() {} static get #a() {} }', SyntaxError);
|
|
assertThrows('class C { static get #a() {} static #a() {} }', SyntaxError);
|
|
assertThrows('class C { static get #a() {} get #a() {} }', SyntaxError);
|
|
assertThrows('class C { static get #a() {} set #a(val) {} }', SyntaxError);
|
|
assertThrows('class C { static get #a() {} #a() {} }', SyntaxError);
|
|
|
|
assertThrows('class C { static set #a(val) {} static set #a(val) {} }', SyntaxError);
|
|
assertThrows('class C { static set #a(val) {} static #a() {} }', SyntaxError);
|
|
assertThrows('class C { static set #a(val) {} get #a() {} }', SyntaxError);
|
|
assertThrows('class C { static set #a(val) {} set #a(val) {} }', SyntaxError);
|
|
assertThrows('class C { static set #a(val) {} #a() {} }', SyntaxError);
|
|
|
|
assertThrows('class C { static #a() {} static #a() {} }', SyntaxError);
|
|
assertThrows('class C { static #a() {} #a(val) {} }', SyntaxError);
|
|
assertThrows('class C { static #a() {} set #a(val) {} }', SyntaxError);
|
|
assertThrows('class C { static #a() {} get #a() {} }', SyntaxError);
|
|
}
|