Title: [272883] trunk
Revision
272883
Author
ticaiol...@gmail.com
Date
2021-02-15 14:40:26 -0800 (Mon, 15 Feb 2021)

Log Message

[ESNext] Implement private accessors
https://bugs.webkit.org/show_bug.cgi?id=194435

Reviewed by Yusuke Suzuki.

JSTests:

* stress/private-accesor-duplicate-name-early-errors.js: Added.
* stress/private-getter-brand-check.js: Added.
* stress/private-getter-inner-class.js: Added.
* stress/private-members-get-and-set.js: Added.
* stress/private-methods-and-accessors-postfix-node.js: Added.
* stress/private-methods-and-accessors-prefix-node.js: Added.
* stress/private-names-available-on-direct-eval.js:
* stress/private-names-available-on-eval-during-field-initialization.js: Copied from JSTests/stress/private-names-available-on-direct-eval.js.
* stress/private-setter-brand-check.js: Added.
* stress/private-setter-inner-class.js: Added.
* test262/config.yaml:
* test262/expectations.yaml:

Source/_javascript_Core:

This patch is implementing support for instance private getters and
setters following the proposal on https://tc39.es/proposal-private-methods.
Private accessors also use the private brand check mechanism of
private methods, which means that we are using both
`op_set_private_brand` and `op_check_private_brand` to perform brand
checks. Accessors are also stored on class lexical scope as a pair of
`getter` and `setter`. This is done creating a new `JSObject` and
storing the `getter` on `get` property, and `setter` on `set`
property. This is designed in such way that we can always hit IC fast
path on `get_by_id_direct` to access the property, and also to allow
constant folding of accessors on DFG/FTL, since acessors objects are
going to be constant once created.

For reference, we have the following bytecode for a private getter
access:

```
class C {
    get #m() {...}
    access() {
        return this.#m;
    }
}
```

Bytecode for class declaration:

```
...
new_object         dst:loc12, inlineCapacity:2 // this is the object to store getter and setter pair
new_func_exp       dst:loc13, scope:loc4, functionDecl:"get #m() {...}"
put_by_id          base:loc13, property:@homeObject, value:loc11, flags:Strict
put_by_id          base:loc12, property:@get, value:loc13, flags:IsDirect|Strict
put_to_scope       scope:loc4, var:#m, value:loc12 // loc4 is the class lexical scope
...

```

Bytecode for `access()`:

```
...
resolve_scope      dst:loc7, scope:loc4, var:"#m", resolveType:GlobalProperty, localScopeDepth:0
get_from_scope     dst:loc8, scope:loc7, var:@privateBrand
check_private_brand base:this, brand:loc8
get_from_scope     dst:loc8, scope:loc7, var:"#m"
get_by_id_direct   dst:loc9, base:loc8, property:@get
mov                dst:loc10, src:this
call               dst:loc6, callee:loc9, argc:1, argv:16
...

```

* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::instantiateLexicalVariables):
(JSC::BytecodeGenerator::getPrivateTraits):
(JSC::BytecodeGenerator::getAvailablePrivateAccessNames):
(JSC::BytecodeGenerator::isPrivateMethod): Deleted.
* bytecompiler/BytecodeGenerator.h:
* bytecompiler/NodesCodegen.cpp:
(JSC::PropertyListNode::emitBytecode):
(JSC::PropertyListNode::emitPutConstantProperty):
(JSC::BaseDotNode::emitGetPropertyValue):
(JSC::BaseDotNode::emitPutProperty):
(JSC::PostfixNode::emitDot):
(JSC::PrefixNode::emitDot):
* parser/Nodes.h:
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseClass):
(JSC::Parser<LexerType>::parseGetterSetter):
* parser/Parser.h:
(JSC::Scope::declarePrivateSetter):
(JSC::Scope::declarePrivateGetter):
* parser/VariableEnvironment.cpp:
(JSC::VariableEnvironment::declarePrivateAccessor):
(JSC::VariableEnvironment::declarePrivateSetter):
(JSC::VariableEnvironment::declarePrivateGetter):
* parser/VariableEnvironment.h:
(JSC::VariableEnvironmentEntry::isPrivateSetter const):
(JSC::VariableEnvironmentEntry::isPrivateGetter const):
(JSC::VariableEnvironmentEntry::setIsPrivateSetter):
(JSC::VariableEnvironmentEntry::setIsPrivateGetter):
(JSC::PrivateNameEntry::isSetter const):
(JSC::PrivateNameEntry::isGetter const):
(JSC::PrivateNameEntry::isField const):
(JSC::PrivateNameEntry::isPrivateMethodOrAcessor const):
(JSC::VariableEnvironment::declarePrivateSetter):
(JSC::VariableEnvironment::declarePrivateGetter):
* runtime/ExceptionHelpers.cpp:
(JSC::createPrivateMethodAccessError):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (272882 => 272883)


--- trunk/JSTests/ChangeLog	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/JSTests/ChangeLog	2021-02-15 22:40:26 UTC (rev 272883)
@@ -1,3 +1,23 @@
+2021-02-15  Caio Lima  <ticaiol...@gmail.com>
+
+        [ESNext] Implement private accessors
+        https://bugs.webkit.org/show_bug.cgi?id=194435
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/private-accesor-duplicate-name-early-errors.js: Added.
+        * stress/private-getter-brand-check.js: Added.
+        * stress/private-getter-inner-class.js: Added.
+        * stress/private-members-get-and-set.js: Added.
+        * stress/private-methods-and-accessors-postfix-node.js: Added.
+        * stress/private-methods-and-accessors-prefix-node.js: Added.
+        * stress/private-names-available-on-direct-eval.js:
+        * stress/private-names-available-on-eval-during-field-initialization.js: Copied from JSTests/stress/private-names-available-on-direct-eval.js.
+        * stress/private-setter-brand-check.js: Added.
+        * stress/private-setter-inner-class.js: Added.
+        * test262/config.yaml:
+        * test262/expectations.yaml:
+
 2021-02-14  Michael Catanzaro  <mcatanz...@gnome.org>
 
         JSC stress test stress/copy-data-properties-fast-path.js.default fails on s390x and ppc64le

Added: trunk/JSTests/stress/private-accesor-duplicate-name-early-errors.js (0 => 272883)


--- trunk/JSTests/stress/private-accesor-duplicate-name-early-errors.js	                        (rev 0)
+++ trunk/JSTests/stress/private-accesor-duplicate-name-early-errors.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,92 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+}
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            set #m(v) { this._v = v; }
+            set #m(u) {}
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            #m;
+            set #m(v) { this._v = v; }
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            set #m(v) { this._v = v; }
+            #m;
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            set #m(v) { this._v = v; }
+            #m() {}
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            #m() {}
+            get #m() {}
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            get #m() {}
+            get #m() {}
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            get #m() {}
+            set #m() {}
+            #m;
+        }
+    `);
+});
+
+assert.throws(SyntaxError, function() {
+    eval(`
+        class C {
+            get #m() {}
+            set #m() {}
+            #m() {}
+        }
+    `);
+});
+

Added: trunk/JSTests/stress/private-getter-brand-check.js (0 => 272883)


--- trunk/JSTests/stress/private-getter-brand-check.js	                        (rev 0)
+++ trunk/JSTests/stress/private-getter-brand-check.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,91 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function () {
+    let createAndInstantiateClass = function () {
+        class C {
+            get #m() { return 'test'; }
+
+            access(o) {
+                return o.#m;
+            }
+        }
+
+        return new C();
+    }
+
+    let c1 = createAndInstantiateClass();
+    let c2 = createAndInstantiateClass();
+
+    assert.sameValue(c1.access(c1), 'test');
+    assert.sameValue(c2.access(c2), 'test');
+
+    assert.throws(TypeError, function() {
+        c1.access(c2);
+    });
+
+    assert.throws(TypeError, function() {
+        c2.access(c1);
+    });
+})();
+
+(function () {
+    class S {
+        get #m() { return 'super class'; }
+
+        superAccess() { return this.#m; }
+    }
+
+    class C extends S {
+        get #m() { return 'subclass'; }
+
+        access() {
+            return this.#m;
+        }
+    }
+
+    let c = new C();
+
+    assert.sameValue(c.access(), 'subclass');
+    assert.sameValue(c.superAccess(), 'super class');
+
+    let s = new S();
+    assert.sameValue(s.superAccess(), 'super class');
+    assert.throws(TypeError, function() {
+        c.access.call(s);
+    });
+})();
+
+(function () {
+    class C {
+        get #m() { return 'test'; }
+
+        access(o) {
+            return o.#m;
+        }
+    }
+
+    let c = new C();
+    assert.sameValue(c.access(c), 'test');
+
+    let o = {};
+    assert.throws(TypeError, function() {
+        c.access(o);
+    });
+})();
+

Added: trunk/JSTests/stress/private-getter-inner-class.js (0 => 272883)


--- trunk/JSTests/stress/private-getter-inner-class.js	                        (rev 0)
+++ trunk/JSTests/stress/private-getter-inner-class.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,159 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function () {
+    class C {
+        get #m() { return 'test'; }
+
+        B = class {
+            method(o) {
+                return o.#m;
+            }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    assert.sameValue(innerB.method(c), 'test');
+})();
+
+(function () {
+    class C {
+        get #m() { return 'outer class'; }
+
+        method() { return this.#m; }
+
+        B = class {
+            method(o) {
+                return o.#m;
+            }
+
+            #m = 'test';
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    assert.sameValue(innerB.method(innerB), 'test');
+    assert.sameValue(c.method(), 'outer class');
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        get #m() { return 'outer class'; }
+
+        method() { return this.#m; }
+
+        B = class {
+            method(o) {
+                return o.#m;
+            }
+
+            get #m() { return 'test'; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    assert.sameValue(innerB.method(innerB), 'test');
+    assert.sameValue(c.method(), 'outer class');
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        get #m() { throw new Error('Should never execute'); }
+
+        B = class {
+            method(o) {
+                return o.#m();
+            }
+
+            #m() { return 'test'; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    assert.sameValue(innerB.method(innerB), 'test');
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        get #m() { return 'outer class'; }
+
+        method() { return this.#m; }
+
+        B = class {
+            method(o) {
+                return o.#m;
+            }
+
+            set #m(v) { this._v = v; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+
+    assert.throws(TypeError, function() {
+        innerB.method(innerB);
+    });
+
+    assert.sameValue(c.method(), 'outer class');
+
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        #m() { return 'outer class'; }
+
+        method() { return this.#m(); }
+
+        B = class {
+            method(o) {
+                return o.#m;
+            }
+
+            get #m() { return 'test262'; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    assert.sameValue(innerB.method(innerB), 'test262');
+    assert.sameValue(c.method(), 'outer class');
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    }, 'accessed inner class getter from an object of outer class');
+    assert.throws(TypeError, function() {
+        C.prototype.method.call(innerB);
+    });
+})();
+

Added: trunk/JSTests/stress/private-members-get-and-set.js (0 => 272883)


--- trunk/JSTests/stress/private-members-get-and-set.js	                        (rev 0)
+++ trunk/JSTests/stress/private-members-get-and-set.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,63 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        accessPrivateMember() {
+            return this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function () {
+        c.accessPrivateMember();
+    });
+})();
+
+(function () {
+    class C {
+        get #m() { return 'test'; }
+
+        accessPrivateMember(v) {
+            this.#m = v;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function () {
+        c.accessPrivateMember('test');
+    });
+})();
+
+(function () {
+    class C {
+        #m() { return 'test'; }
+
+        accessPrivateMember(v) {
+            this.#m = v;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function () {
+        c.accessPrivateMember('test');
+    });
+})();
+

Added: trunk/JSTests/stress/private-methods-and-accessors-postfix-node.js (0 => 272883)


--- trunk/JSTests/stress/private-methods-and-accessors-postfix-node.js	                        (rev 0)
+++ trunk/JSTests/stress/private-methods-and-accessors-postfix-node.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,166 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function() {
+    class C {
+        #method() {
+            throw new Error("Should never be called");
+        }
+
+        access() {
+            return this.#method++;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    let executedGetter = false;
+    class C {
+        get #m() {
+            executedGetter = true;
+        }
+
+        access() {
+            return this.#m++;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+
+    assert.sameValue(true, executedGetter);
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            throw new Error("Should never be executed");
+        }
+
+        access() {
+            return this.#m++;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            assert.sameValue(5, v);
+        }
+
+        get #m() {
+            return 4;
+        }
+
+        access() {
+            return this.#m++;
+        }
+    }
+
+    let c = new C();
+    assert.sameValue(4, c.access());
+})();
+
+// Ignored result
+
+(function() {
+    class C {
+        #method() {
+            throw new Error("Should never be called");
+        }
+
+        access() {
+            this.#method--;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    let executedGetter = false;
+    class C {
+        get #m() {
+            executedGetter = true;
+        }
+
+        access() {
+            this.#m--;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+
+    assert.sameValue(true, executedGetter);
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            throw new Error("Should never be executed");
+        }
+
+        access() {
+            this.#m--;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            assert.sameValue(3, v);
+        }
+
+        get #m() {
+            return 4;
+        }
+
+        access() {
+            this.#m--;
+        }
+    }
+
+    let c = new C();
+    c.access();
+})();
+

Added: trunk/JSTests/stress/private-methods-and-accessors-prefix-node.js (0 => 272883)


--- trunk/JSTests/stress/private-methods-and-accessors-prefix-node.js	                        (rev 0)
+++ trunk/JSTests/stress/private-methods-and-accessors-prefix-node.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,166 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function() {
+    class C {
+        #method() {
+            throw new Error("Should never be called");
+        }
+
+        access() {
+            return ++this.#method;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    let executedGetter = false;
+    class C {
+        get #m() {
+            executedGetter = true;
+        }
+
+        access() {
+            return ++this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+
+    assert.sameValue(true, executedGetter);
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            throw new Error("Should never be executed");
+        }
+
+        access() {
+            return ++this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            assert.sameValue(5, v);
+        }
+
+        get #m() {
+            return 4;
+        }
+
+        access() {
+            return ++this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.sameValue(5, c.access());
+})();
+
+// Ignored result
+
+(function() {
+    class C {
+        #method() {
+            throw new Error("Should never be called");
+        }
+
+        access() {
+            --this.#method;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    let executedGetter = false;
+    class C {
+        get #m() {
+            executedGetter = true;
+        }
+
+        access() {
+            --this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+
+    assert.sameValue(true, executedGetter);
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            throw new Error("Should never be executed");
+        }
+
+        access() {
+            --this.#m;
+        }
+    }
+
+    let c = new C();
+    assert.throws(TypeError, function() {
+        c.access();
+    });
+})();
+
+(function() {
+    class C {
+        set #m(v) {
+            assert.sameValue(3, v);
+        }
+
+        get #m() {
+            return 4;
+        }
+
+        access() {
+            --this.#m;
+        }
+    }
+
+    let c = new C();
+    c.access();
+})();
+

Modified: trunk/JSTests/stress/private-names-available-on-direct-eval.js (272882 => 272883)


--- trunk/JSTests/stress/private-names-available-on-direct-eval.js	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/JSTests/stress/private-names-available-on-direct-eval.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -21,3 +21,36 @@
     assert.sameValue(c.callMethodFromEval(), 'test');
 })();
 
+(function () {
+    class C {
+        get #m() {
+            return 'test';
+        }
+
+        callGetterFromEval() {
+            let self = this;
+            return eval('self.#m');
+        }
+    }
+
+    let c = new C();
+    assert.sameValue(c.callGetterFromEval(), 'test');
+})();
+
+(function () {
+    class C {
+        set #m(v) {
+            this._v = v;
+        }
+
+        callSetterFromEval(v) {
+            let self = this;
+            eval('self.#m = v');
+        }
+    }
+
+    let c = new C();
+    c.callSetterFromEval('test')
+    assert.sameValue(c._v, 'test');
+})();
+

Copied: trunk/JSTests/stress/private-names-available-on-eval-during-field-initialization.js (from rev 272882, trunk/JSTests/stress/private-names-available-on-direct-eval.js) (0 => 272883)


--- trunk/JSTests/stress/private-names-available-on-eval-during-field-initialization.js	                        (rev 0)
+++ trunk/JSTests/stress/private-names-available-on-eval-during-field-initialization.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,31 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + rhs + " bug got: " + lhs);
+    }
+};
+
+(function () {
+    class C {
+        #m() { return 'test'; }
+
+        field = eval('this.#m()');
+    }
+
+    let c = new C();
+    assert.sameValue(c.field, 'test');
+})();
+
+(function () {
+    class C {
+        get #m() { return 'test'; }
+
+        field = eval('this.#m');
+    }
+
+    let c = new C();
+    assert.sameValue(c.field, 'test');
+})();
+

Added: trunk/JSTests/stress/private-setter-brand-check.js (0 => 272883)


--- trunk/JSTests/stress/private-setter-brand-check.js	                        (rev 0)
+++ trunk/JSTests/stress/private-setter-brand-check.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,100 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function () {
+    let createAndInstantiateClass = function () {
+        class C {
+            set #m(v) { this._v = v; }
+
+            access(o, v) {
+                o.#m = v;
+            }
+        }
+
+        let c = new C();
+        return c;
+    };
+
+    let c1 = createAndInstantiateClass();
+    let c2 = createAndInstantiateClass();
+
+    c1.access(c1, 'test');
+    assert.sameValue(c1._v, 'test');
+    c2.access(c2, 'test');
+    assert.sameValue(c2._v, 'test');
+
+    assert.throws(TypeError, function() {
+        c1.access(c2, 'foo');
+    });
+
+    assert.throws(TypeError, function() {
+        c2.access(c1, 'foo');
+    });
+})();
+
+(function () {
+    class S {
+        set #m(v) { this._v = v }
+
+        superAccess(v) { this.#m = v; }
+    }
+
+    class C extends S {
+        set #m(v) { this._u = v; }
+
+        access(v) {
+            return this.#m = v;
+        }
+    }
+
+    let c = new C();
+
+    c.access('test');
+    assert.sameValue(c._u, 'test');
+
+    c.superAccess('super class');
+    assert.sameValue(c._v, 'super class');
+
+    let s = new S();
+    s.superAccess('super class')
+    assert.sameValue(s._v, 'super class');
+
+    assert.throws(TypeError, function() {
+        c.access.call(s, 'foo');
+    });
+})();
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        access(o, v) {
+            return o.#m = v;
+        }
+    }
+
+    let c = new C();
+    c.access(c, 'test');
+    assert.sameValue(c._v, 'test');
+
+    let o = {};
+    assert.throws(TypeError, function() {
+        c.access(o, 'foo');
+    });
+})();
+

Added: trunk/JSTests/stress/private-setter-inner-class.js (0 => 272883)


--- trunk/JSTests/stress/private-setter-inner-class.js	                        (rev 0)
+++ trunk/JSTests/stress/private-setter-inner-class.js	2021-02-15 22:40:26 UTC (rev 272883)
@@ -0,0 +1,155 @@
+//@ requireOptions("--usePrivateMethods=true")
+
+let assert = {
+    sameValue: function (lhs, rhs) {
+        if (lhs !== rhs)
+            throw new Error("Expected: " + lhs + " bug got: " + rhs);
+    },
+
+    throws: function (expectedError, op) {
+        try {
+          op();
+        } catch(e) {
+            if (!(e instanceof expectedError))
+                throw new Error("Expected to throw: " + expectedError + " but threw: " + e);
+        }
+    }
+};
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        B = class {
+            method(o, v) {
+                o.#m = v;
+            }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+    innerB.method(c, 'test');
+    assert.sameValue(c._v, 'test');
+})();
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        method(v) { this.#m = v; }
+
+        B = class {
+            method(o, v) {
+                o.#m = v;
+            }
+
+            get m() { return this.#m; }
+
+            #m;
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+
+    innerB.method(innerB, 'test');
+    assert.sameValue(innerB.m, 'test');
+
+    c.method('outer class');
+    assert.sameValue(c._v, 'outer class');
+
+    assert.throws(TypeError, function() {
+        innerB.method(c, 'foo');
+    });
+})();
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        method(v) { this.#m = v; }
+
+        B = class {
+            method(o, v) {
+                o.#m = v;
+            }
+
+            get #m() { return 'test'; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+
+    assert.throws(TypeError, function() {
+        innerB.method(innerB);
+    });
+
+    c.method('outer class');
+    assert.sameValue(c._v, 'outer class');
+
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        method(v) { this.#m = v; }
+
+        B = class {
+            method(o, v) {
+                o.#m = v;
+            }
+
+            #m() { return 'test'; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+
+    assert.throws(TypeError, function() {
+        innerB.method(innerB, 'foo');
+    });
+
+    c.method('outer class');
+    assert.sameValue(c._v, 'outer class');
+
+    assert.throws(TypeError, function() {
+        innerB.method(c);
+    });
+})();
+
+(function () {
+    class C {
+        set #m(v) { this._v = v; }
+
+        method(v) { this.#m = v; }
+
+        B = class {
+            method(o, v) {
+                o.#m = v;
+            }
+
+            set #m(v) { this._v = v; }
+        }
+    }
+
+    let c = new C();
+    let innerB = new c.B();
+
+    innerB.method(innerB, 'test262');
+    assert.sameValue(innerB._v, 'test262');
+
+    c.method('outer class');
+    assert.sameValue(c._v, 'outer class');
+
+    assert.throws(TypeError, function() {
+        innerB.method(c, 'foo');
+    });
+})();
+

Modified: trunk/JSTests/test262/config.yaml (272882 => 272883)


--- trunk/JSTests/test262/config.yaml	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/JSTests/test262/config.yaml	2021-02-15 22:40:26 UTC (rev 272883)
@@ -4,6 +4,7 @@
   WeakRef: useWeakRefs
   FinalizationRegistry: useWeakRefs
   class-fields-private: usePrivateClassFields
+  class-methods-private: usePrivateMethods
   class-static-fields-public: usePublicStaticClassFields
   class-static-fields-private: usePrivateStaticClassFields
   Intl.DateTimeFormat-dayPeriod: useIntlDateTimeFormatDayPeriod
@@ -19,7 +20,6 @@
     - regexp-lookbehind
     - legacy-regexp
 
-    - class-methods-private
     - class-static-methods-private
     - cleanupSome
     - host-gc-required

Modified: trunk/JSTests/test262/expectations.yaml (272882 => 272883)


--- trunk/JSTests/test262/expectations.yaml	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/JSTests/test262/expectations.yaml	2021-02-15 22:40:26 UTC (rev 272883)
@@ -741,6 +741,12 @@
 test/built-ins/Function/prototype/toString/method-object.js:
   default: 'Test262Error: Conforms to NativeFunction Syntax: "function f( /* b */ ) /* c */ { /* d */ }" (f /* a */ ( /* b */ ) /* c */ { /* d */ })'
   strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function f( /* b */ ) /* c */ { /* d */ }" (f /* a */ ( /* b */ ) /* c */ { /* d */ })'
+test/built-ins/Function/prototype/toString/private-method-class-_expression_.js:
+  default: 'Test262Error: Conforms to NativeFunction Syntax: "function #f( /* b */ ) /* c */ { /* d */ }" (#f /* a */ ( /* b */ ) /* c */ { /* d */ })'
+  strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function #f( /* b */ ) /* c */ { /* d */ }" (#f /* a */ ( /* b */ ) /* c */ { /* d */ })'
+test/built-ins/Function/prototype/toString/private-method-class-statement.js:
+  default: 'Test262Error: Conforms to NativeFunction Syntax: "function #f( /* b */ ) /* c */ { /* d */ }" (#f /* a */ ( /* b */ ) /* c */ { /* d */ })'
+  strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function #f( /* b */ ) /* c */ { /* d */ }" (#f /* a */ ( /* b */ ) /* c */ { /* d */ })'
 test/built-ins/Function/prototype/toString/setter-class-_expression_-static.js:
   default: 'Test262Error: Conforms to NativeFunction Syntax: "function ( /* c */ a /* d */ ) /* e */ { /* f */ }" (set /* a */ f /* b */ ( /* c */ a /* d */ ) /* e */ { /* f */ })'
   strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function ( /* c */ a /* d */ ) /* e */ { /* f */ }" (set /* a */ f /* b */ ( /* c */ a /* d */ ) /* e */ { /* f */ })'

Modified: trunk/Source/_javascript_Core/ChangeLog (272882 => 272883)


--- trunk/Source/_javascript_Core/ChangeLog	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-02-15 22:40:26 UTC (rev 272883)
@@ -1,3 +1,101 @@
+2021-02-15  Caio Lima  <ticaiol...@gmail.com>
+
+        [ESNext] Implement private accessors
+        https://bugs.webkit.org/show_bug.cgi?id=194435
+
+        Reviewed by Yusuke Suzuki.
+
+        This patch is implementing support for instance private getters and
+        setters following the proposal on https://tc39.es/proposal-private-methods.
+        Private accessors also use the private brand check mechanism of
+        private methods, which means that we are using both
+        `op_set_private_brand` and `op_check_private_brand` to perform brand
+        checks. Accessors are also stored on class lexical scope as a pair of
+        `getter` and `setter`. This is done creating a new `JSObject` and
+        storing the `getter` on `get` property, and `setter` on `set`
+        property. This is designed in such way that we can always hit IC fast
+        path on `get_by_id_direct` to access the property, and also to allow
+        constant folding of accessors on DFG/FTL, since acessors objects are
+        going to be constant once created.
+
+        For reference, we have the following bytecode for a private getter
+        access:
+
+        ```
+        class C {
+            get #m() {...}
+            access() {
+                return this.#m;
+            }
+        }
+        ```
+
+        Bytecode for class declaration:
+
+        ```
+        ...
+        new_object         dst:loc12, inlineCapacity:2 // this is the object to store getter and setter pair
+        new_func_exp       dst:loc13, scope:loc4, functionDecl:"get #m() {...}"
+        put_by_id          base:loc13, property:@homeObject, value:loc11, flags:Strict
+        put_by_id          base:loc12, property:@get, value:loc13, flags:IsDirect|Strict
+        put_to_scope       scope:loc4, var:#m, value:loc12 // loc4 is the class lexical scope
+        ...
+
+        ```
+
+        Bytecode for `access()`:
+
+        ```
+        ...
+        resolve_scope      dst:loc7, scope:loc4, var:"#m", resolveType:GlobalProperty, localScopeDepth:0
+        get_from_scope     dst:loc8, scope:loc7, var:@privateBrand
+        check_private_brand base:this, brand:loc8
+        get_from_scope     dst:loc8, scope:loc7, var:"#m"
+        get_by_id_direct   dst:loc9, base:loc8, property:@get
+        mov                dst:loc10, src:this
+        call               dst:loc6, callee:loc9, argc:1, argv:16
+        ...
+
+        ```
+
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::instantiateLexicalVariables):
+        (JSC::BytecodeGenerator::getPrivateTraits):
+        (JSC::BytecodeGenerator::getAvailablePrivateAccessNames):
+        (JSC::BytecodeGenerator::isPrivateMethod): Deleted.
+        * bytecompiler/BytecodeGenerator.h:
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::PropertyListNode::emitBytecode):
+        (JSC::PropertyListNode::emitPutConstantProperty):
+        (JSC::BaseDotNode::emitGetPropertyValue):
+        (JSC::BaseDotNode::emitPutProperty):
+        (JSC::PostfixNode::emitDot):
+        (JSC::PrefixNode::emitDot):
+        * parser/Nodes.h:
+        * parser/Parser.cpp:
+        (JSC::Parser<LexerType>::parseClass):
+        (JSC::Parser<LexerType>::parseGetterSetter):
+        * parser/Parser.h:
+        (JSC::Scope::declarePrivateSetter):
+        (JSC::Scope::declarePrivateGetter):
+        * parser/VariableEnvironment.cpp:
+        (JSC::VariableEnvironment::declarePrivateAccessor):
+        (JSC::VariableEnvironment::declarePrivateSetter):
+        (JSC::VariableEnvironment::declarePrivateGetter):
+        * parser/VariableEnvironment.h:
+        (JSC::VariableEnvironmentEntry::isPrivateSetter const):
+        (JSC::VariableEnvironmentEntry::isPrivateGetter const):
+        (JSC::VariableEnvironmentEntry::setIsPrivateSetter):
+        (JSC::VariableEnvironmentEntry::setIsPrivateGetter):
+        (JSC::PrivateNameEntry::isSetter const):
+        (JSC::PrivateNameEntry::isGetter const):
+        (JSC::PrivateNameEntry::isField const):
+        (JSC::PrivateNameEntry::isPrivateMethodOrAcessor const):
+        (JSC::VariableEnvironment::declarePrivateSetter):
+        (JSC::VariableEnvironment::declarePrivateGetter):
+        * runtime/ExceptionHelpers.cpp:
+        (JSC::createPrivateMethodAccessError):
+
 2021-02-15  Commit Queue  <commit-qu...@webkit.org>
 
         Unreviewed, reverting r272831.

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (272882 => 272883)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp	2021-02-15 22:40:26 UTC (rev 272883)
@@ -56,8 +56,8 @@
 #include "UnlinkedModuleProgramCodeBlock.h"
 #include "UnlinkedProgramCodeBlock.h"
 #include <wtf/BitVector.h>
+#include <wtf/HashSet.h>
 #include <wtf/Optional.h>
-#include <wtf/SmallPtrSet.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/text/WTFString.h>
 
@@ -1900,10 +1900,17 @@
 
             // FIXME: only do this if there is an eval() within a nested scope --- otherwise it isn't needed.
             // https://bugs.webkit.org/show_bug.cgi?id=206663
-            if (entry.value.isPrivateField())
-                symbolTable->addPrivateName(entry.key.get(), PrivateNameEntry(PrivateNameEntry::Traits::IsDeclared));
-            else if (entry.value.isPrivateMethod())
-                symbolTable->addPrivateName(entry.key.get(), PrivateNameEntry(PrivateNameEntry::Traits::IsDeclared | PrivateNameEntry::Traits::IsMethod));
+            
+            const PrivateNameEnvironment* privateEnvironment = lexicalVariables.privateNameEnvironment();
+            if (!privateEnvironment)
+                continue;
+
+            auto findResult = privateEnvironment->find(entry.key.get());
+
+            if (findResult == privateEnvironment->end())
+                continue;
+
+            symbolTable->addPrivateName(findResult->key.get(), findResult->value);
         }
     }
     return hasCapturedVariables;
@@ -2931,16 +2938,18 @@
     }
 }
 
-bool BytecodeGenerator::isPrivateMethod(const Identifier& ident)
+// This should be called only with PrivateNames available.
+PrivateNameEntry BytecodeGenerator::getPrivateTraits(const Identifier& ident)
 {
     for (unsigned i = m_privateNamesStack.size(); i--; ) {
         auto& map = m_privateNamesStack[i];
         auto it = map.find(ident.impl());
         if (it != map.end())
-            return it->value.isMethod();
+            return it->value;
     }
 
-    return false;
+    RELEASE_ASSERT_NOT_REACHED();
+    return PrivateNameEntry();
 }
 
 void BytecodeGenerator::pushPrivateAccessNames(const PrivateNameEnvironment* environment)
@@ -2981,17 +2990,13 @@
 Optional<PrivateNameEnvironment> BytecodeGenerator::getAvailablePrivateAccessNames()
 {
     PrivateNameEnvironment result;
-    SmallPtrSet<UniquedStringImpl*, 16> excludedNames;
+    HashSet<UniquedStringImpl*> excludedNames;
     for (unsigned i = m_privateNamesStack.size(); i--; ) {
         auto& map = m_privateNamesStack[i];
         for (auto& entry : map)  {
-            if (entry.value.isPrivateMethodOrAcessor()) {
-                if (!excludedNames.contains(entry.key.get())) {
-                    result.add(entry.key, entry.value);
-                    excludedNames.add(entry.key.get());
-                }
-            } else
-                excludedNames.add(entry.key.get());
+            auto addResult = excludedNames.add(entry.key.get());
+            if (addResult.isNewEntry)
+                result.add(entry.key, entry.value);
         }
     }
 

Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h (272882 => 272883)


--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h	2021-02-15 22:40:26 UTC (rev 272883)
@@ -1263,7 +1263,7 @@
             m_lastInstruction = prevLastInstruction;
         }
 
-        bool isPrivateMethod(const Identifier&);
+        PrivateNameEntry getPrivateTraits(const Identifier&);
 
         void pushPrivateAccessNames(const PrivateNameEnvironment*);
         void popPrivateAccessNames();

Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (272882 => 272883)


--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2021-02-15 22:40:26 UTC (rev 272883)
@@ -579,6 +579,57 @@
 
 RegisterID* PropertyListNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dstOrConstructor, RegisterID* prototype, Vector<JSTextPosition>* instanceFieldLocations, Vector<JSTextPosition>* staticFieldLocations)
 {
+    using GetterSetterPair = std::pair<PropertyNode*, PropertyNode*>;
+    using GetterSetterMap = HashMap<UniquedStringImpl*, GetterSetterPair, IdentifierRepHash>;
+
+    if (hasPrivateAccessors()) {
+        GetterSetterMap privateAccessorMap;
+
+        for (PropertyListNode* propertyList = this; propertyList; propertyList = propertyList->m_next) {
+            if (!(propertyList->m_node->type() & (PropertyNode::PrivateGetter | PropertyNode::PrivateSetter)))
+                continue;
+
+            // We group private getters and setters to store them in a object
+            GetterSetterPair pair(propertyList->m_node, static_cast<PropertyNode*>(nullptr));
+            GetterSetterMap::AddResult result = privateAccessorMap.add(propertyList->m_node->name()->impl(), pair);
+            auto& resultPair = result.iterator->value;
+            // If the map already contains an element with node->name(),
+            // we need to store this node in the second part.
+            if (!result.isNewEntry)
+                resultPair.second = propertyList->m_node;
+            continue;
+        }
+
+        // Then we declare private accessors
+        for (auto& it : privateAccessorMap) {
+            // FIXME: Use GetterSetter to store private accessors
+            // https://bugs.webkit.org/show_bug.cgi?id=221915
+            RefPtr<RegisterID> getterSetterObj = generator.emitNewObject(generator.newTemporary());
+            GetterSetterPair pair = it.value;
+
+            auto emitPutAccessor = [&] (PropertyNode* propertyNode) {
+                RegisterID* base = propertyNode->isInstanceClassProperty() ? prototype : dstOrConstructor;
+
+                RefPtr<RegisterID> value = generator.emitNode(propertyNode->m_assign);
+                if (propertyNode->needsSuperBinding())
+                    emitPutHomeObject(generator, value.get(), base);
+                auto setterOrGetterIdent = propertyNode->m_type & PropertyNode::PrivateGetter
+                    ? generator.propertyNames().builtinNames().getPrivateName()
+                    : generator.propertyNames().builtinNames().setPrivateName();
+                generator.emitDirectPutById(getterSetterObj.get(), setterOrGetterIdent, value.get());
+            };
+
+            if (pair.first)
+                emitPutAccessor(pair.first);
+
+            if (pair.second)
+                emitPutAccessor(pair.second);
+
+            Variable var = generator.variable(*pair.first->name());
+            generator.emitPutToScope(generator.scopeRegister(), var, getterSetterObj.get(), DoNotThrowIfNotFound, InitializationMode::ConstInitialization);
+        }
+    }
+
     PropertyListNode* p = this;
     RegisterID* dst = nullptr;
 
@@ -586,6 +637,9 @@
     for (; p && (p->m_node->m_type & PropertyNode::Constant); p = p->m_next) {
         dst = p->m_node->isInstanceClassProperty() ? prototype : dstOrConstructor;
 
+        if (p->m_node->type() & (PropertyNode::PrivateGetter | PropertyNode::PrivateSetter))
+            continue;
+
         if (p->isComputedClassField())
             emitSaveComputedFieldName(generator, *p->m_node);
 
@@ -610,8 +664,6 @@
         // a computed property or a spread, just emit everything as that may override previous values.
         bool canOverrideProperties = false;
 
-        typedef std::pair<PropertyNode*, PropertyNode*> GetterSetterPair;
-        typedef HashMap<UniquedStringImpl*, GetterSetterPair, IdentifierRepHash> GetterSetterMap;
         GetterSetterMap instanceMap;
         GetterSetterMap staticMap;
 
@@ -651,6 +703,9 @@
             if (p->isComputedClassField())
                 emitSaveComputedFieldName(generator, *p->m_node);
 
+            if (p->m_node->type() & (PropertyNode::PrivateGetter | PropertyNode::PrivateSetter))
+                continue;
+
             if (p->isInstanceClassField()) {
                 ASSERT(instanceFieldLocations);
                 ASSERT(node->m_type & PropertyNode::Constant);
@@ -782,6 +837,8 @@
 
     if (node.isClassProperty()) {
         ASSERT(node.needsSuperBinding());
+        ASSERT(!(node.type() & PropertyNode::PrivateSetter));
+        ASSERT(!(node.type() & PropertyNode::PrivateGetter));
 
         if (node.type() & PropertyNode::PrivateMethod) {
             Variable var = generator.variable(*node.name());
@@ -919,16 +976,44 @@
 RegisterID* BaseDotNode::emitGetPropertyValue(BytecodeGenerator& generator, RegisterID* dst, RegisterID* base, RefPtr<RegisterID>& thisValue)
 {
     if (isPrivateMember()) {
-        if (generator.isPrivateMethod(identifier())) {
-            Variable var = generator.variable(identifier());
+        auto identifierName = identifier();
+        auto privateTraits = generator.getPrivateTraits(identifierName);
+        if (privateTraits.isMethod()) {
+            Variable var = generator.variable(identifierName);
             RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
 
-            RegisterID* privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
-            generator.emitCheckPrivateBrand(base, privateBrandSymbol);
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base, privateBrandSymbol.get());
 
             return generator.emitGetFromScope(dst, scope.get(), var, ThrowIfNotFound);
         }
 
+        if (privateTraits.isGetter()) {
+            Variable var = generator.variable(identifierName);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base, privateBrandSymbol.get());
+
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> getterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().getPrivateName());
+            CallArguments args(generator, nullptr);
+            generator.move(args.thisRegister(), base);
+            return generator.emitCall(dst, getterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+        }
+
+        if (privateTraits.isSetter()) {
+            // We need to perform brand check to follow the spec
+            Variable var = generator.variable(identifierName);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base, privateBrandSymbol.get());
+            generator.emitThrowTypeError("Trying to access an undefined private getter");
+            return dst;
+        }
+
+        ASSERT(privateTraits.isField());
         Variable var = generator.variable(m_ident);
         ASSERT_WITH_MESSAGE(!var.local(), "Private Field names must be stored in captured variables");
 
@@ -957,16 +1042,36 @@
 {
     if (isPrivateMember()) {
         auto identifierName = identifier();
-        if (generator.isPrivateMethod(identifierName)) {
+        auto privateTraits = generator.getPrivateTraits(identifierName);
+        if (privateTraits.isSetter()) {
             Variable var = generator.variable(identifierName);
             RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
 
-            RegisterID* privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
-            generator.emitCheckPrivateBrand(base, privateBrandSymbol);
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base, privateBrandSymbol.get());
 
-            generator.emitThrowTypeError("Trying to access a not defined private setter");
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> setterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().setPrivateName());
+            CallArguments args(generator, nullptr, 1);
+            generator.move(args.thisRegister(), base);
+            generator.move(args.argumentRegister(0), value);
+            generator.emitCall(generator.newTemporary(), setterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+
+            return value;
         }
 
+        if (privateTraits.isGetter() || privateTraits.isMethod()) {
+            Variable var = generator.variable(identifierName);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base, privateBrandSymbol.get());
+
+            generator.emitThrowTypeError("Trying to access an undefined private setter");
+            return value;
+        }
+
+        ASSERT(privateTraits.isField());
         Variable var = generator.variable(m_ident);
         ASSERT_WITH_MESSAGE(!var.local(), "Private Field names must be stored in captured variables");
 
@@ -2287,16 +2392,67 @@
 
     if (dotAccessor->isPrivateMember()) {
         ASSERT(!baseIsSuper);
+        auto privateTraits = generator.getPrivateTraits(ident);
+
+        if (privateTraits.isField()) {
+            Variable var = generator.variable(ident);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+            RefPtr<RegisterID> privateName = generator.newTemporary();
+            generator.emitGetFromScope(privateName.get(), scope.get(), var, DoNotThrowIfNotFound);
+
+            RefPtr<RegisterID> value = generator.emitGetPrivateName(generator.newTemporary(), base.get(), privateName.get());
+            RefPtr<RegisterID> oldValue = emitPostIncOrDec(generator, generator.tempDestination(dst), value.get(), m_operator);
+            generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+            generator.emitPrivateFieldPut(base.get(), privateName.get(), value.get());
+            generator.emitProfileType(value.get(), divotStart(), divotEnd());
+            return generator.move(dst, oldValue.get());
+        }
+
+        if (privateTraits.isMethod()) {
+            Variable var = generator.variable(ident);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base.get(), privateBrandSymbol.get());
+
+            generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+            generator.emitThrowTypeError("Trying to access an undefined private setter");
+            return generator.tempDestination(dst);
+        }
+
         Variable var = generator.variable(ident);
         RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
-        RefPtr<RegisterID> privateName = generator.newTemporary();
-        generator.emitGetFromScope(privateName.get(), scope.get(), var, DoNotThrowIfNotFound);
 
-        RefPtr<RegisterID> value = generator.emitGetPrivateName(generator.newTemporary(), base.get(), privateName.get());
+        RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+        generator.emitCheckPrivateBrand(base.get(), privateBrandSymbol.get());
+
+        RefPtr<RegisterID> value;
+        if (privateTraits.isGetter()) {
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> getterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().getPrivateName());
+            CallArguments args(generator, nullptr);
+            generator.move(args.thisRegister(), base.get());
+            value = generator.emitCall(generator.newTemporary(), getterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+        } else {
+            generator.emitThrowTypeError("Trying to access an undefined private getter");
+            return generator.tempDestination(dst);
+        }
+
         RefPtr<RegisterID> oldValue = emitPostIncOrDec(generator, generator.tempDestination(dst), value.get(), m_operator);
         generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
-        generator.emitPrivateFieldPut(base.get(), privateName.get(), value.get());
-        generator.emitProfileType(value.get(), divotStart(), divotEnd());
+
+        if (privateTraits.isSetter()) {
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> setterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().setPrivateName());
+            CallArguments args(generator, nullptr, 1);
+            generator.move(args.thisRegister(), base.get());
+            generator.move(args.argumentRegister(0), value.get());
+            generator.emitCall(generator.newTemporary(), setterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+            generator.emitProfileType(value.get(), divotStart(), divotEnd());
+            return generator.move(dst, oldValue.get());
+        } 
+
+        generator.emitThrowTypeError("Trying to access an undefined private getter");
         return generator.move(dst, oldValue.get());
     }
 
@@ -2525,17 +2681,66 @@
     generator.emitExpressionInfo(dotAccessor->divot(), dotAccessor->divotStart(), dotAccessor->divotEnd());
     RegisterID* value;
     if (dotAccessor->isPrivateMember()) {
-        ASSERT(!baseNode->isSuperNode());
+        auto privateTraits = generator.getPrivateTraits(ident);
+        if (privateTraits.isField()) {
+            ASSERT(!baseNode->isSuperNode());
+            Variable var = generator.variable(ident);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+            RefPtr<RegisterID> privateName = generator.newTemporary();
+            generator.emitGetFromScope(privateName.get(), scope.get(), var, DoNotThrowIfNotFound);
+
+            value = generator.emitGetPrivateName(propDst.get(), base.get(), privateName.get());
+            emitIncOrDec(generator, value, m_operator);
+            generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+            generator.emitPrivateFieldPut(base.get(), privateName.get(), value);
+            generator.emitProfileType(value, divotStart(), divotEnd());
+            return generator.move(dst, propDst.get());
+        }
+
+        if (privateTraits.isMethod()) {
+            Variable var = generator.variable(ident);
+            RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
+
+            RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+            generator.emitCheckPrivateBrand(base.get(), privateBrandSymbol.get());
+
+            generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
+            generator.emitThrowTypeError("Trying to access an undefined private setter");
+            return generator.move(dst, propDst.get());
+        }
+
         Variable var = generator.variable(ident);
         RefPtr<RegisterID> scope = generator.emitResolveScope(nullptr, var);
-        RefPtr<RegisterID> privateName = generator.newTemporary();
-        generator.emitGetFromScope(privateName.get(), scope.get(), var, DoNotThrowIfNotFound);
 
-        value = generator.emitGetPrivateName(propDst.get(), base.get(), privateName.get());
+        RefPtr<RegisterID> privateBrandSymbol = generator.emitGetPrivateBrand(generator.newTemporary(), scope.get());
+        generator.emitCheckPrivateBrand(base.get(), privateBrandSymbol.get());
+
+        if (privateTraits.isGetter()) {
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> getterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().getPrivateName());
+            CallArguments args(generator, nullptr);
+            generator.move(args.thisRegister(), base.get());
+            value = generator.emitCall(propDst.get(), getterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+        } else {
+            generator.emitThrowTypeError("Trying to access an undefined private getter");
+            return generator.move(dst, propDst.get());
+        }
+
         emitIncOrDec(generator, value, m_operator);
         generator.emitExpressionInfo(divot(), divotStart(), divotEnd());
-        generator.emitPrivateFieldPut(base.get(), privateName.get(), value);
-        generator.emitProfileType(value, divotStart(), divotEnd());
+
+        if (privateTraits.isSetter()) {
+            RefPtr<RegisterID> getterSetterObj = generator.emitGetFromScope(generator.newTemporary(), scope.get(), var, ThrowIfNotFound);
+            RefPtr<RegisterID> setterFunction = generator.emitDirectGetById(generator.newTemporary(), getterSetterObj.get(), generator.propertyNames().builtinNames().setPrivateName());
+            CallArguments args(generator, nullptr, 1);
+            generator.move(args.thisRegister(), base.get());
+            generator.move(args.argumentRegister(0), value);
+            generator.emitCall(generator.newTemporary(), setterFunction.get(), NoExpectedFunction, args, m_position, m_position, m_position, DebuggableCall::Yes);
+            generator.emitProfileType(value, divotStart(), divotEnd());
+            return generator.move(dst, propDst.get());
+        } 
+
+        generator.emitThrowTypeError("Trying to access an undefined private getter");
         return generator.move(dst, propDst.get());
     }
 

Modified: trunk/Source/_javascript_Core/parser/Nodes.h (272882 => 272883)


--- trunk/Source/_javascript_Core/parser/Nodes.h	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/parser/Nodes.h	2021-02-15 22:40:26 UTC (rev 272883)
@@ -721,7 +721,7 @@
     enum class ClassElementTag : uint8_t { No, Instance, Static, LastTag };
     class PropertyNode final : public ParserArenaFreeable {
     public:
-        enum Type : uint8_t { Constant = 1, Getter = 2, Setter = 4, Computed = 8, Shorthand = 16, Spread = 32, PrivateField = 64, PrivateMethod = 128 };
+        enum Type : uint16_t { Constant = 1, Getter = 2, Setter = 4, Computed = 8, Shorthand = 16, Spread = 32, PrivateField = 64, PrivateMethod = 128, PrivateSetter = 256, PrivateGetter = 512 };
 
         PropertyNode(const Identifier&, ExpressionNode*, Type, SuperBinding, ClassElementTag);
         PropertyNode(ExpressionNode*, Type, SuperBinding, ClassElementTag);
@@ -740,7 +740,7 @@
         bool isInstanceClassField() const { return isInstanceClassProperty() && !needsSuperBinding(); }
         bool isStaticClassField() const { return isStaticClassProperty() && !needsSuperBinding(); }
         bool isOverriddenByDuplicate() const { return m_isOverriddenByDuplicate; }
-        bool isPrivate() const { return m_type & (PrivateField | PrivateMethod); }
+        bool isPrivate() const { return m_type & (PrivateField | PrivateMethod | PrivateGetter | PrivateSetter); }
         bool hasComputedName() const { return m_expression; }
         bool isComputedClassField() const { return isClassField() && hasComputedName(); }
         void setIsOverriddenByDuplicate() { m_isOverriddenByDuplicate = true; }
@@ -760,7 +760,7 @@
         const Identifier* m_name;
         ExpressionNode* m_expression;
         ExpressionNode* m_assign;
-        unsigned m_type;
+        unsigned m_type : 10;
         unsigned m_needsSuperBinding : 1;
         static_assert(1 << 2 > static_cast<unsigned>(ClassElementTag::LastTag), "ClassElementTag shouldn't use more than two bits");
         unsigned m_classElementTag : 2;
@@ -788,6 +788,16 @@
             return m_node->isStaticClassField();
         }
 
+        void setHasPrivateAccessors(bool hasPrivateAccessors)
+        {
+            m_hasPrivateAccessors = hasPrivateAccessors;
+        }
+
+        bool hasPrivateAccessors() const
+        {
+            return m_hasPrivateAccessors;
+        }
+
         static bool shouldCreateLexicalScopeForClass(PropertyListNode*);
 
         RegisterID* emitBytecode(BytecodeGenerator&, RegisterID*, RegisterID*, Vector<JSTextPosition>*, Vector<JSTextPosition>*);
@@ -804,6 +814,7 @@
 
         PropertyNode* m_node;
         PropertyListNode* m_next { nullptr };
+        bool m_hasPrivateAccessors { false };
     };
 
     class ObjectLiteralNode final : public ExpressionNode {

Modified: trunk/Source/_javascript_Core/parser/Parser.cpp (272882 => 272883)


--- trunk/Source/_javascript_Core/parser/Parser.cpp	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/parser/Parser.cpp	2021-02-15 22:40:26 UTC (rev 272883)
@@ -2880,6 +2880,7 @@
     classScope->preventVarDeclarations();
     classScope->setStrictMode();
     bool declaresPrivateMethod = false;
+    bool declaresPrivateAccessor = false;
     next();
 
     ASSERT_WITH_MESSAGE(requirements != FunctionNameRequirements::Unnamed, "Currently, there is no caller that uses FunctionNameRequirements::Unnamed for class syntax.");
@@ -2978,7 +2979,7 @@
             bool escaped = m_token.m_data.escaped;
             ASSERT(ident);
             next();
-            if (parseMode == SourceParseMode::MethodMode && !escaped && (matchIdentifierOrKeyword() || match(STRING) || match(DOUBLE) || match(INTEGER) || match(BIGINT) || match(OPENBRACKET))) {
+            if (parseMode == SourceParseMode::MethodMode && !escaped && (matchIdentifierOrKeyword() || match(STRING) || match(DOUBLE) || match(INTEGER) || match(BIGINT) || match(OPENBRACKET) || (Options::usePrivateMethods() && match(PRIVATENAME)))) {
                 isGetter = *ident == propertyNames.get;
                 isSetter = *ident == propertyNames.set;
             }
@@ -3006,6 +3007,7 @@
             ASSERT(ident);
             next();
             if (Options::usePrivateMethods() && match(OPENPAREN)) {
+                semanticFailIfTrue(tag == ClassElementTag::Static, "Cannot declare a static private method");
                 semanticFailIfTrue(classScope->declarePrivateMethod(*ident) & DeclarationResult::InvalidDuplicateDeclaration, "Cannot declare private method twice");
                 declaresPrivateMethod = true;
                 type = static_cast<PropertyNode::Type>(type | PropertyNode::PrivateMethod);
@@ -3025,8 +3027,21 @@
 
         TreeProperty property;
         if (isGetter || isSetter) {
-            type = static_cast<PropertyNode::Type>(type & ~PropertyNode::Constant);
-            type = static_cast<PropertyNode::Type>(type | (isGetter ? PropertyNode::Getter : PropertyNode::Setter));
+            if (Options::usePrivateMethods() && match(PRIVATENAME)) {
+                ident = m_token.m_data.ident;
+                if (isSetter) {
+                    semanticFailIfTrue(classScope->declarePrivateSetter(*ident) & DeclarationResult::InvalidDuplicateDeclaration, "Declared private setter with an already used name");
+                    declaresPrivateAccessor = true;
+                    type = static_cast<PropertyNode::Type>(type | PropertyNode::PrivateSetter);
+                } else {
+                    semanticFailIfTrue(classScope->declarePrivateGetter(*ident) & DeclarationResult::InvalidDuplicateDeclaration, "Declared private getter with an already used name");
+                    declaresPrivateAccessor = true;
+                    type = static_cast<PropertyNode::Type>(type | PropertyNode::PrivateGetter);
+                }
+            } else {
+                type = static_cast<PropertyNode::Type>(type & ~PropertyNode::Constant);
+                type = static_cast<PropertyNode::Type>(type | (isGetter ? PropertyNode::Getter : PropertyNode::Setter));
+            }
             property = parseGetterSetter(context, type, methodStart, ConstructorKind::None, tag);
             failIfFalse(property, "Cannot parse this method");
         } else if (!match(OPENPAREN) && (tag == ClassElementTag::Instance || Options::usePublicStaticClassFields()) && parseMode == SourceParseMode::MethodMode) {
@@ -3100,7 +3115,7 @@
     info.endOffset = tokenLocation().endOffset - 1;
     consumeOrFail(CLOSEBRACE, "Expected a closing '}' after a class body");
 
-    if (declaresPrivateMethod) {
+    if (declaresPrivateMethod || declaresPrivateAccessor) {
         Identifier privateBrandIdentifier = m_vm.propertyNames->builtinNames().privateBrandPrivateName();
         DeclarationResultMask declarationResult = classScope->declareLexicalVariable(&privateBrandIdentifier, true);
         ASSERT_UNUSED(declarationResult, declarationResult == DeclarationResult::Valid);
@@ -3108,6 +3123,11 @@
         classScope->addClosedVariableCandidateUnconditionally(privateBrandIdentifier.impl());
     }
 
+    if constexpr (std::is_same_v<TreeBuilder, ASTBuilder>) {
+        if (classElements)
+            classElements->setHasPrivateAccessors(declaresPrivateAccessor);
+    }
+
     if (Options::usePrivateClassFields()) {
         // Fail if there are no parent private name scopes and any used-but-undeclared private names.
         semanticFailIfFalse(copyUndeclaredPrivateNamesToOuterScope(), "Cannot reference undeclared private names");
@@ -4428,12 +4448,18 @@
 
     JSTokenLocation location(tokenLocation());
 
-    if (matchSpecIdentifier() || match(STRING) || m_token.m_type & KeywordTokenFlag) {
+    bool matchesPrivateName = match(PRIVATENAME);
+    if (matchSpecIdentifier() || match(STRING) || (Options::usePrivateMethods() && matchesPrivateName) || m_token.m_type & KeywordTokenFlag) {
         stringPropertyName = m_token.m_data.ident;
         semanticFailIfTrue(tag == ClassElementTag::Static && *stringPropertyName == m_vm.propertyNames->prototype,
             "Cannot declare a static method named 'prototype'");
         semanticFailIfTrue(tag == ClassElementTag::Instance && *stringPropertyName == m_vm.propertyNames->constructor,
             "Cannot declare a getter or setter named 'constructor'");
+
+        if (match(PRIVATENAME)) {
+            semanticFailIfTrue(tag == ClassElementTag::No, "Cannot declare a private setter or getter outside a class");
+            semanticFailIfTrue(tag == ClassElementTag::Static, "Cannot declare a private setter or getter as static");
+        }
         next();
     } else if (match(DOUBLE) || match(INTEGER)) {
         numericPropertyName = m_token.m_data.doubleValue;
@@ -4454,10 +4480,18 @@
         failIfFalse(match(OPENPAREN), "Expected a parameter list for getter definition");
         SetForScope<SourceParseMode> innerParseMode(m_parseMode, SourceParseMode::GetterMode);
         failIfFalse((parseFunctionInfo(context, FunctionNameRequirements::Unnamed, false, constructorKind, SuperBinding::Needed, getterOrSetterStartOffset, info, FunctionDefinitionType::Method)), "Cannot parse getter definition");
-    } else {
+    } else if (type & PropertyNode::Setter) {
         failIfFalse(match(OPENPAREN), "Expected a parameter list for setter definition");
         SetForScope<SourceParseMode> innerParseMode(m_parseMode, SourceParseMode::SetterMode);
         failIfFalse((parseFunctionInfo(context, FunctionNameRequirements::Unnamed, false, constructorKind, SuperBinding::Needed, getterOrSetterStartOffset, info, FunctionDefinitionType::Method)), "Cannot parse setter definition");
+    } else if (type & PropertyNode::PrivateSetter) {
+        failIfFalse(match(OPENPAREN), "Expected a parameter list for private setter definition");
+        SetForScope<SourceParseMode> innerParseMode(m_parseMode, SourceParseMode::SetterMode);
+        failIfFalse((parseFunctionInfo(context, FunctionNameRequirements::Unnamed, false, constructorKind, SuperBinding::Needed, getterOrSetterStartOffset, info, FunctionDefinitionType::Method)), "Cannot parse private setter definition");
+    } else if (type & PropertyNode::PrivateGetter) {
+        failIfFalse(match(OPENPAREN), "Expected a parameter list for private getter definition");
+        SetForScope<SourceParseMode> innerParseMode(m_parseMode, SourceParseMode::GetterMode);
+        failIfFalse((parseFunctionInfo(context, FunctionNameRequirements::Unnamed, false, constructorKind, SuperBinding::Needed, getterOrSetterStartOffset, info, FunctionDefinitionType::Method)), "Cannot parse private getter definition");
     }
 
     if (stringPropertyName)

Modified: trunk/Source/_javascript_Core/parser/Parser.h (272882 => 272883)


--- trunk/Source/_javascript_Core/parser/Parser.h	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/parser/Parser.h	2021-02-15 22:40:26 UTC (rev 272883)
@@ -517,6 +517,34 @@
         return result;
     }
 
+    DeclarationResultMask declarePrivateSetter(const Identifier& ident)
+    {
+        ASSERT(m_allowsLexicalDeclarations);
+        DeclarationResultMask result = DeclarationResult::Valid;
+        bool addResult = m_lexicalVariables.declarePrivateSetter(ident);
+
+        if (!addResult) {
+            result |= DeclarationResult::InvalidDuplicateDeclaration;
+            return result;
+        }
+
+        return result;
+    }
+
+    DeclarationResultMask declarePrivateGetter(const Identifier& ident)
+    {
+        ASSERT(m_allowsLexicalDeclarations);
+        DeclarationResultMask result = DeclarationResult::Valid;
+        bool addResult = m_lexicalVariables.declarePrivateGetter(ident);
+
+        if (!addResult) {
+            result |= DeclarationResult::InvalidDuplicateDeclaration;
+            return result;
+        }
+
+        return result;
+    }
+
     DeclarationResultMask declarePrivateField(const Identifier& ident)
     {
         ASSERT(m_allowsLexicalDeclarations);

Modified: trunk/Source/_javascript_Core/parser/VariableEnvironment.cpp (272882 => 272883)


--- trunk/Source/_javascript_Core/parser/VariableEnvironment.cpp	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/parser/VariableEnvironment.cpp	2021-02-15 22:40:26 UTC (rev 272883)
@@ -104,6 +104,80 @@
     findResult->value.setIsExported();
 }
 
+bool VariableEnvironment::declarePrivateAccessor(const RefPtr<UniquedStringImpl>& identifier, PrivateNameEntry::Traits accessorTraits)
+{
+    if (!m_rareData)
+        m_rareData = WTF::makeUnique<VariableEnvironment::RareData>();
+
+    auto findResult = m_rareData->m_privateNames.find(identifier);
+
+    if (findResult == m_rareData->m_privateNames.end()) {
+        PrivateNameEntry meta(PrivateNameEntry::Traits::IsDeclared | accessorTraits);
+
+        auto entry = VariableEnvironmentEntry();
+        if (accessorTraits == PrivateNameEntry::Traits::IsSetter)
+            entry.setIsPrivateSetter();
+        else {
+            ASSERT(accessorTraits == PrivateNameEntry::Traits::IsGetter);
+            entry.setIsPrivateGetter();
+        }
+        entry.setIsConst();
+        entry.setIsCaptured();
+        m_map.add(identifier, entry);
+
+        auto addResult = m_rareData->m_privateNames.add(identifier, meta);
+        return addResult.isNewEntry;
+    }
+
+    PrivateNameEntry currentEntry = findResult->value;
+    if (currentEntry.isDeclared()) {
+        if ((accessorTraits == PrivateNameEntry::Traits::IsSetter && !currentEntry.isGetter())
+            || (accessorTraits == PrivateNameEntry::Traits::IsGetter && !currentEntry.isSetter()))
+            return false; // Error: declaring a duplicate private accessor.
+
+        PrivateNameEntry meta(currentEntry.bits() | accessorTraits);
+        m_rareData->m_privateNames.set(identifier, meta);
+
+        auto entryIterator = m_map.find(identifier);
+        ASSERT(entryIterator != m_map.end());
+        if (accessorTraits == PrivateNameEntry::Traits::IsSetter)
+            entryIterator->value.setIsPrivateSetter();
+        else {
+            ASSERT(accessorTraits == PrivateNameEntry::Traits::IsGetter);
+            entryIterator->value.setIsPrivateGetter();
+        }
+
+        return true;
+    }
+
+    // it was previously used, mark it as declared.
+    auto entry = VariableEnvironmentEntry();
+    if (accessorTraits == PrivateNameEntry::Traits::IsSetter)
+        entry.setIsPrivateSetter();
+    else {
+        ASSERT(accessorTraits == PrivateNameEntry::Traits::IsGetter);
+        entry.setIsPrivateGetter();
+    }
+    entry.setIsConst();
+    entry.setIsCaptured();
+    m_map.add(identifier, entry);
+
+    PrivateNameEntry newEntry(currentEntry.bits() | PrivateNameEntry::Traits::IsDeclared | accessorTraits);
+    m_rareData->m_privateNames.set(identifier, newEntry);
+    return true;
+
+}
+
+bool VariableEnvironment::declarePrivateSetter(const RefPtr<UniquedStringImpl>& identifier)
+{
+    return declarePrivateAccessor(identifier, PrivateNameEntry::Traits::IsSetter);
+}
+
+bool VariableEnvironment::declarePrivateGetter(const RefPtr<UniquedStringImpl>& identifier)
+{
+    return declarePrivateAccessor(identifier, PrivateNameEntry::Traits::IsGetter);
+}
+
 bool VariableEnvironment::declarePrivateMethod(const RefPtr<UniquedStringImpl>& identifier)
 {
     if (!m_rareData)

Modified: trunk/Source/_javascript_Core/parser/VariableEnvironment.h (272882 => 272883)


--- trunk/Source/_javascript_Core/parser/VariableEnvironment.h	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/parser/VariableEnvironment.h	2021-02-15 22:40:26 UTC (rev 272883)
@@ -47,6 +47,8 @@
     ALWAYS_INLINE bool isSloppyModeHoistingCandidate() const { return m_bits & IsSloppyModeHoistingCandidate; }
     ALWAYS_INLINE bool isPrivateField() const { return m_bits & IsPrivateField; }
     ALWAYS_INLINE bool isPrivateMethod() const { return m_bits & IsPrivateMethod; }
+    ALWAYS_INLINE bool isPrivateSetter() const { return m_bits & IsPrivateSetter; }
+    ALWAYS_INLINE bool isPrivateGetter() const { return m_bits & IsPrivateGetter; }
 
     ALWAYS_INLINE void setIsCaptured() { m_bits |= IsCaptured; }
     ALWAYS_INLINE void setIsConst() { m_bits |= IsConst; }
@@ -60,6 +62,8 @@
     ALWAYS_INLINE void setIsSloppyModeHoistingCandidate() { m_bits |= IsSloppyModeHoistingCandidate; }
     ALWAYS_INLINE void setIsPrivateField() { m_bits |= IsPrivateField; }
     ALWAYS_INLINE void setIsPrivateMethod() { m_bits |= IsPrivateMethod; }
+    ALWAYS_INLINE void setIsPrivateSetter() { m_bits |= IsPrivateSetter; }
+    ALWAYS_INLINE void setIsPrivateGetter() { m_bits |= IsPrivateGetter; }
 
     ALWAYS_INLINE void clearIsVar() { m_bits &= ~IsVar; }
 
@@ -84,6 +88,8 @@
         IsSloppyModeHoistingCandidate = 1 << 9,
         IsPrivateField = 1 << 10,
         IsPrivateMethod = 1 << 11,
+        IsPrivateGetter = 1 << 12,
+        IsPrivateSetter = 1 << 13,
     };
     uint16_t m_bits { 0 };
 };
@@ -101,8 +107,11 @@
     ALWAYS_INLINE bool isUsed() const { return m_bits & IsUsed; }
     ALWAYS_INLINE bool isDeclared() const { return m_bits & IsDeclared; }
     ALWAYS_INLINE bool isMethod() const { return m_bits & IsMethod; }
+    ALWAYS_INLINE bool isSetter() const { return m_bits & IsSetter; }
+    ALWAYS_INLINE bool isGetter() const { return m_bits & IsGetter; }
+    ALWAYS_INLINE bool isField() const { return !isPrivateMethodOrAcessor(); }
 
-    bool isPrivateMethodOrAcessor() const { return isMethod(); }
+    bool isPrivateMethodOrAcessor() const { return isMethod() || isSetter() || isGetter(); }
 
     ALWAYS_INLINE void setIsUsed() { m_bits |= IsUsed; }
     ALWAYS_INLINE void setIsDeclared() { m_bits |= IsDeclared; }
@@ -119,6 +128,8 @@
         IsUsed = 1 << 0,
         IsDeclared = 1 << 1,
         IsMethod = 1 << 2,
+        IsGetter = 1 << 3,
+        IsSetter = 1 << 4,
     };
 
 private:
@@ -195,6 +206,14 @@
     bool declarePrivateMethod(const Identifier& identifier) { return declarePrivateMethod(identifier.impl()); }
     bool declarePrivateMethod(const RefPtr<UniquedStringImpl>& identifier);
 
+    bool declarePrivateAccessor(const RefPtr<UniquedStringImpl>&, PrivateNameEntry::Traits);
+
+    bool declarePrivateSetter(const Identifier& identifier) { return declarePrivateSetter(identifier.impl()); }
+    bool declarePrivateSetter(const RefPtr<UniquedStringImpl>& identifier);
+
+    bool declarePrivateGetter(const Identifier& identifier) { return declarePrivateGetter(identifier.impl()); }
+    bool declarePrivateGetter(const RefPtr<UniquedStringImpl>& identifier);
+
     Map::AddResult declarePrivateField(const RefPtr<UniquedStringImpl>& identifier)
     {
         auto& meta = getOrAddPrivateName(identifier.get());

Modified: trunk/Source/_javascript_Core/runtime/ExceptionHelpers.cpp (272882 => 272883)


--- trunk/Source/_javascript_Core/runtime/ExceptionHelpers.cpp	2021-02-15 22:28:02 UTC (rev 272882)
+++ trunk/Source/_javascript_Core/runtime/ExceptionHelpers.cpp	2021-02-15 22:40:26 UTC (rev 272883)
@@ -336,7 +336,7 @@
 
 JSObject* createPrivateMethodAccessError(JSGlobalObject* globalObject)
 {
-    return createTypeError(globalObject, makeString("Cannot access private method"_s), defaultSourceAppender, TypeNothing);
+    return createTypeError(globalObject, makeString("Cannot access private method or acessor"_s), defaultSourceAppender, TypeNothing);
 }
 
 JSObject* createReinstallPrivateMethodError(JSGlobalObject* globalObject)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to