Proposal About Private Symbol

# Gary Guo (10 years ago)

I have just sent this proposal through www.ecma-international.org/memento/register_TC39_Royalty_Free_Task_Group.php but I don't know what more I need to do. I am a high school student from China who is interested in next-generation ecmascript and has been working on an ecmascript implementation for months. Since I am not a native speaker, there might be grammar mistakes in following texts. I have sent this proposal to this mailing list this about 8 hours ago but to some reason it is not delivered.

Here is my proposal:

As known, Symbol Type introduced in ECAMScript provides a way to create private fields, and feature extensions without interfering with current usage, since each Symbol created is unique. However, sometimes software developers may not want to expose a private field to public, so they might not want to let the symbol to be easily accessed from Object.getOwnPropertySymbols function.

A change to the current Symbol constructor can be made to change this situation. Instead of using Symbol([Description]), we may use Symbol([Description], [Private]). When the private parameter is set to true, the symbol will be eliminated from Object.getOwnPropertySymbols. In later text, I will call such a Symbol private symbol.

This change will not only benefit developers, but also helps to clarify the standard. The ECMAScript 5.1 uses internal properties, while the ECMAScript Harmony uses internal slots as a way to define the properties that are not public accessible but exists in an object. However, this adds to the difficulty of implementing such objects with internal properties/slots. If private symbol is supported by the implementation, the implementation can simply just create a property of that object, with property key a private symbol. For example, we can say there is a private well known symbol (that is not exposed to user code) Symbol([[BooleanData]]), and we can eliminate the description about internal slot and express texts in a more uniform way.

This also allows more standard-defined objects to be implemented in ECMAScript itself, by only exposing these private symbols to certain ECMAScript modules that implement features required by the Standard. I uses the way that describes below to implement my own ECMAScript implementation.

For example:```js // PrivateSymbol.js var BooleanData = Symbol("[[BooleanData]]", true); export {BooleanData}

// Boolean.js import {BooleanData} from 'PrivateSymbol.js' var global=this; global.Boolean = function(x){ if(this==global){ return !!x; }else{ this[BooleanData]=!!x; } }

// User Code var b=new Boolean(true); b["[[BooleanData]]"] → undefined // guaranteed by semantics of Symbol b[Symbol("[[BooleanData]]")] → undefined // guaranteed by semantics of Symbol b.getOwnPropertySymbols() → [] // guaranteed since Symbol is private``` So there is still no way to access or modify its [[BooleanData]] property, so the property is equivalent to internal slots/properties, while the way to describe them is uniformed.

# Rick Waldron (10 years ago)

On Sat Dec 20 2014 at 7:54:19 AM Gary Guo <nbdd0121 at hotmail.com> wrote:

I have just sent this proposal through www.ecma-international.org/memento/register_TC39_Royalty_Free_Task_Group.php but I don't know what more I need to do. I am a high school student from China who is interested in next-generation ecmascript and has been working on an ecmascript implementation for months.

That's excellent!

Since I am not a native speaker, there might be grammar mistakes in following texts. I have sent this proposal to this mailing list this about 8 hours ago but to some reason it is not delivered.

Sometimes the "spam" mail trap is too aggressive, but glad to see this came through.

Here is my proposal:

As known, Symbol Type introduced in ECAMScript provides a way to create private fields,

This is misunderstood, Symbol creates a unique, non-string value that may be used as a property key. There is nothing about Symbol instances that makes them private—only unique. Private "things" were deferred to ES7.

and feature extensions without interfering with current usage, since each Symbol created is unique. However, sometimes software developers may not want to expose a private field to public, so they might not want to let the symbol to be easily accessed from Object.getOwnPropertySymbols function.

A change to the current Symbol constructor can be made to change this situation. Instead of using Symbol([Description]), we may use Symbol([Description], [Private]).

When the private parameter is set to true, the symbol will be eliminated from Object.getOwnPropertySymbols. In later text, I will call such a Symbol private symbol.

This change will not only benefit developers, but also helps to clarify the standard. The ECMAScript 5.1 uses internal properties, while the ECMAScript Harmony uses internal slots as a way to define the properties that are not public accessible but exists in an object. However, this adds to the difficulty of implementing such objects with internal properties/slots. If private symbol is supported by the implementation, the implementation can simply just create a property of that object, with property key a private symbol. For example, we can say there is a private well known symbol (that is not exposed to user code) Symbol([[BooleanData]]), and we can eliminate the description about internal slot and express texts in a more uniform way.

This also allows more standard-defined objects to be implemented in ECMAScript itself, by only exposing these private symbols to certain ECMAScript modules that implement features required by the Standard. I uses the way that describes below to implement my own ECMAScript implementation.

For example:

// PrivateSymbol.js
var BooleanData = Symbol("[[BooleanData]]", true);
export {BooleanData}

// Boolean.js
import {BooleanData} from 'PrivateSymbol.js'
var global=this;
global.Boolean = function(x){
    if(this==global){
        return !!x;
    }else{
        this[BooleanData]=!!x;
    }
}

// User Code
var b=new Boolean(true);
b["[[BooleanData]]"] → undefined // guaranteed by semantics of Symbol
b[Symbol("[[BooleanData]]")] → undefined // guaranteed by semantics of
Symbol
b.getOwnPropertySymbols() → [] // guaranteed since Symbol is private

So there is still no way to access or modify its [[BooleanData]] property, so the property is equivalent to internal slots/properties, while the way to describe them is uniformed.

Similar proposals have been made in the past:

(Beginning at and evolving through...)

Ultimately it was decided that Symbol is just a symbol and that "private" things will be dealt with orthogonally (and at a later date).

# Domenic Denicola (10 years ago)

For more reasons on why a simple "private symbol" approach does not quite work, see zenparsing/es-abstract-refs#11

# Gary Guo (10 years ago)

I don't think it's a problem though. As long as the private Symbol doesn't leak, there is no way to access private properties. Private Symbol as I supposed only eliminate itself from getOwnPropertySymbols, and that's it, there should not be no more constraints

# Gary Guo (10 years ago)

Oops, seems Outlook.com ruins my email. One more time

I don't think it's a problem though. As long as the private Symbol doesn't leak, there is no way to access private properties. Private Symbol as I supposed only eliminate itself from getOwnPropertySymbols, and that's it, there should not be no more constraints on private Symbol.

var constructor=function(){
    var privateField=Symbol('private', true);
    var pubField=Symbol('public');
    var leakedField=Symbol('leak', true);
    return function something(arg){
        this[privateField]=arg;
        this[pubField]=arg;
        this[leakedField]=arg;
        this["leak"]=leakedField;
    }
}
var arg="A";
var instance=new constructor(arg); // there is no way to access private fields
arg===instance[Object.getOwnPropertySymbols()[0]] // True, since Object.getOwnPropertySymbols expose the symbol
arg===instance[instance.leak]; // True, since instance.leak expose the symbol

Under careful use of the symbols, and without Object.getOwnPropertySymbols leaking every symbol, we can use symbols as private field. V8 already has implemented private Symbol (just one more boolean field when defining the struct symbol) though it is not exposed to Script-side.

# Domenic Denicola (10 years ago)

Did you read the linked post? The problem is completely different from the one you describe. It is about interoperability with membranes.

# Gary Guo (10 years ago)

One link there are 4 assumptions about private symbol, while I think the existence of first one is sufficient.

  1. They would not be discoverable by Object.getOwnPropertySymbols

This is what I suggest.

  1. They would not invoke any traps on proxies.
  2. They would not tunnel through proxies to proxy targets.
  3. Getting a private-symbol-keyed property would not traverse the prototype chain of the object (perhaps arguable).

Unnecessary, as long as symbol doesn't leak to external environment, I don't think we need to impose these constraints. Without these constraints I did not see any problems there.

# Kevin Smith (10 years ago)

Under careful use of the symbols, and without Object.getOwnPropertySymbols leaking every symbol, we can use symbols as private field.

There are other ways that symbols can leak besides getOwnPropertySymbols. Take a look at proxies, which allow you to intercept [[Get]] and [[Set]].

In fact, in my opinion [[Get]] and [[Set]] in the current MOP are antithetical to privacy by definition, since the property key must be transmitted to arbitrary exotic objects.

Anyway, in order to make symbols work for privacy, you would need to make sure that:

  • They are not exposed via getOwnPropertySymbols
  • They are not exposed to proxies
  • They are not exposed to arbitrary exotic objects

You would also have to accept the fact that private symbols would simply not function across membranes. That would be sad.

Furthermore, private symbols will tend to create a vector for confused deputy-style attacks, since it is very easy to accidentally use [[Set]] to create a private symbol on a "foreign" object, thereby accidentally branding an object from an unknown source.

const _p = Symbol("", true); // <= private, presumably
class Oops {
    constructor() {
        this[_p] = 0;
    }
    mutateMe() {
        this[_p] = 1; // Easy bug
    }
}
var x = {};
Oops.prototype.mutateMe.call(x);
// x now has _p, but wasn't constructed by Oops

V8 already has implemented private Symbol (just one more boolean field when

defining the struct symbol) though it is not exposed to Script-side.

It's true that V8 has implemented something, although the extent to which that something is private is unknown to me since there's no spec and I haven't read the code. From looking at V8's promise implementation, though, it seems that ensuring correctness of private symbol usage is tricky.

# Kevin Smith (10 years ago)
  1. They would not invoke any traps on proxies.
  2. They would not tunnel through proxies to proxy targets.
  3. Getting a private-symbol-keyed property would not traverse the prototype chain of the object (perhaps arguable). Unnecessary, as long as symbol doesn't leak to external environment, I don't think we need to impose these constraints. Without these constraints I did not see any problems there.

You simply cannot allow 2 and 3 and still call them private symbols. If you allow 2, then an attacker can discover private symbols by creating a proxy for an object which uses them in one of its methods. If you allow 3, then private symbols are an unmediated communication channel across membranes.

# Gary Guo (10 years ago)

Technically speaking there is no way to prevent such a attack, since in the debugger everything can be exposed to external environment. Careful check is still needed with private symbols according to my proposal.

var constructor=function(){
    'use strict';
    var sym=Symbol('private', true);
    var identifier=Symbol(undefined, true);
    var ret=function(){
        if(this===undefined)throw Error('Cannot be called as function');
        this[Symbol.for('id')]=identifier;
    };
    ret.prototype.set=function(){
        if(this[Symbol.for('id')]!=identifier)throw Error('Invalid This');
        this[sym]=1;
    }
    return ret.bind(undefined);
}();
# Gary Guo (10 years ago)

Oops, mistakes found. I just ignored the fact that in this particular way the Symbol can be retrieved. It seems impossible to have an idea of private symbol in this way. In the case, I think the language can provide a way to distinguish whether an object is created directly by a class/constructor. It seems that there will be no way to prevent that unless the language ensures this.

For example, Add a well-known Symbol @@constructor, and when constructing an object, make sure any objects have a property @@constructor and has the descriptor {enumerable:false, configurable:false, writable: false, value: The constructor that actually creates the object}

var constructor=function(){
    'use strict';
    var sym=Symbol('private', true);
    var ret=function(){
    };
    ret.prototype.set=function(){
        if(this[Symbol.constructor]!=ret)throw Error('Invalid This');
        this[sym]=1;
    }
    return ret.bind(undefined);
}();
# Tab Atkins Jr. (10 years ago)

On Sat, Dec 20, 2014 at 10:41 PM, Gary Guo <nbdd0121 at hotmail.com> wrote:

Oops, mistakes found. I just ignored the fact that in this particular way the Symbol can be retrieved. It seems impossible to have an idea of private symbol in this way. In the case, I think the language can provide a way to distinguish whether an object is created directly by a class/constructor. It seems that there will be no way to prevent that unless the language ensures this.

For example, Add a well-known Symbol @@constructor, and when constructing an object, make sure any objects have a property @@constructor and has the descriptor {enumerable:false, configurable:false, writable: false, value: The constructor that actually creates the object}

Hostile code can just grab that well-known symbol and brand their own objects with it, indistinguishably from the language.

There are ways around this, too - create your own Symbol, keep it closure-private to the class, so it doesn't escape the class's methods. Have the constructor take an extra argument that must be equal to this symbol; if it's not, the constructor instead calls itself, passing all of its arguments through and adding the special identity Symbol, and just returns that. That way you know that your "this" object is definitely a fresh language-created one, and you're definitely being called as a constructor, not as a function invoked on some arbitrary object.

# Gary Guo (10 years ago)

From: jackalmage at gmail.com Date: Sun, 21 Dec 2014 00:45:40 -0800> There are ways around this, too - create your own Symbol, keep it closure-private to the class, so it doesn't escape the class's methods. Have the constructor take an extra argument that must be equal to this symbol; if it's not, the constructor instead calls itself, passing all of its arguments through and adding the special identity Symbol, and just returns that. That way you know that your "this" object is definitely a fresh language-created one, and you're definitely being called as a constructor, not as a function invoked on some arbitrary object.

There is already a way to workaround like this in ES5.1 to ensure constructor are called as expectedjsfunction packAsConstructor(construct){ 'use strict'; return function(){ if(this===undefined)throw Error('Invalid call as non-constructor'); if(construct.prototype) Object.setPrototypeOf(this, construct.prototype); return construct.apply(this, arguments); }.bind(undefined);} But this is not the core of the problem. The problem is the Proxy introduced in ES6 enables an object to capture and override almost any operation on an object. Without operation on object, it becomes very costly (by using an Array of created objects and compare each of them) to identify whether a object is faked or valid.

# Michał Wadas (10 years ago)

But this is not the core of the problem. The problem is the Proxy

introduced in ES6 enables an object to capture and override almost any operation on an object. Without operation on object, it becomes very costly (by using an Array of created objects and compare each of them) to identify whether a object is faked or valid. ES6 WeakSet provides you O(1) object check and don't cause memory leak.

# Gary Guo (10 years ago)

Oops, I forget the WeakSet. So seems my private symbol proposal can work now. Under very deliberately design, private symbol can be used as private field. jsvar constructor=function(){ 'use strict'; var allObjects=new WeakSet(); var privateSymbol=Symbol('private', true); var ret=function(){ if(this===undefined)throw Error('Invalid Construction'); this[privateSymbol]=1; allObjects.add(this); } ret.prototype.set=function(sth){ if(!allObject.has(this))throw Error('Invalid Call'); this[privateSymbol]=sth; // Now this can be called safely, no more worry about leak to Proxy } ret.bind(undefined);}

# Domenic Denicola (10 years ago)

Yes, if you use weak sets in combination with private symbols you can more or less emulate weak maps. But why not just use weak maps in that case?

# Gary Guo (10 years ago)

I didn't quite get the point when private symbols pass through the proxy. What will cause the problem in such a situation? How about the proxy get/set the target without passing through handler? For examplejsvar psym=Symbol('private', true);var obj={};var proxy=new Proxy(obj, { get:function(){console.log('Capture');}, set:function(){console.log('Capture');});proxy[psym] // without triggering get handlerproxy[psym]='sth' // without triggering set handlerJust simply doesn't grant the proxy the right to operate on private symbols

# Domenic Denicola (10 years ago)

That breaks membranes.

# Kevin Smith (10 years ago)
var constructor=function(){
    'use strict';
    var allObjects=new WeakSet();
    var privateSymbol=Symbol('private', true);
    var ret=function(){
        if(this===undefined)throw Error('Invalid Construction');
        this[privateSymbol]=1;
        allObjects.add(this);
    }
    ret.prototype.set=function(sth){
        if(!allObject.has(this))throw Error('Invalid Call');
        this[privateSymbol]=sth; // Now this can be called safely, no more
worry about leak to Proxy
    }
    ret.bind(undefined);
}

One of the goals of any "object-private state" proposal has to be:

  • It should be easy to create private-state-carrying abstractions which are "safe",
  • It should be easy to validate such abstractions as "safe".

I think the code above is a great illustration of how private symbols fail to satisfy these goals.