Are Private name and Weak Map the same feature? and the Assoc API
There is a paragraph in the original e-mail that I forgot. Adding it below:
Le 16/12/2011 12:19, David Bruant a écrit :
Hi,
I couldn't post on es-discuss yesterday, so I rewrote my message and posted it on github [1]. Here is the markdown source:
PART 1: The same feature?
When looked abstractly, both private name and weak maps propose an equivalent feature: a mapping between 2 unforgeable references and a value. Both allow to share a secret with someone assuming the knowledge of 2 unforgeable entities. Both have the interesting property of symmetric non-discoverability:
- Given an object, you can't list all private names
- Given a private name, you can't list all objects which have this name as a property
- Given a weak map, you can't list all objects used as keys
- Given an object, you can't list all weak maps using this object as key
This can be used in WeakMaps to optimize garbage collection. As it turns out, the same thing stands for private names: If nothing holds a reference to a private name, all related slots in objects can be garbage-collected as well.
There are a couple of differences:
- A private property can be made non-configurable & non-writable
- A private name can refer to an accessor property
I claim that these can be reimplemented by overriding the WeakMap API.
I'm open to discussion on whether there are cases that can be implemented with one API and not the other. For the rest of this message, I'll assume that it's the same feature with 2 different syntax.
PART 2: A more generic syntax
Unfortunately, private names and WeakMaps (and maps and sets...) both define a special new sort of object to work. What about an API that would allow to associate any 2 objects and bind a "secret" value to the association?
// In one part of the code var o1 = {}, o2 = {}; var a = Assoc(o1, o2); a.set(37); // ... // In another component which has a reference to both o1 and o2 var myA = Assoc(o1, o2); myA.get(); // 37
In order to unseal a secret associated with 2 objects, you need a reference to both, that's it. Exactly like with WeakMaps, exactly like with private names.
Part 2.1: where I pretend I can reimplement WeakMaps with the Assoc API
WeakMap = function(){ var weakMapIdentity = {}; var presence = {}; return { get: function(o){ return Assoc(weakMapIdentity, o).get(); }, set: function(o, val){ Assoc(presence, o).set(true); return Assoc(weakMapIdentity, o).set(val); }, has: function(o){ return !!Assoc(presence, o).get(); }, delete = function(o){ Assoc(presence, o).set(undefined); } } }
Interestingly, 2 identities are required to emulate weakmaps. One for the values (weakMapIdentity) and one to consider presence of keys. In my opinion, the WeakMap API should be separated into 2 APIs. One with get/set and the other with add/remove/has which would find the natural name of WeakSet.
Part 2.2: where I go further and get a bit crazy
Some properties:
- Assoc() === Assoc // why waste a reference? :-p
- Assoc(o) !== o
- Assoc(o) === Assoc(o)
- Assoc(Assoc(o)) === Assoc(o)
- Assoc(o, o) === Assoc(o)
- Assoc(o1, o2) === Assoc(o2, o1)
The Assoc API could be extended with any number of arguments. The resulting object represents the association of all objects passed as arguments.
- Assoc(o1, o2, o3) === Assoc(Assoc(o1, o2), o3) === Assoc(o1, Assoc(o2, o3))
- ...and these applies to any number of arguments.
Long story short, the Assoc function only cares about whether or not you pass it the right references to unseal the secret. And you should not care about passing the objects in the "right order". If I want to keep a secret from you, I'll create an unforgeable reference; I won't try to "obscure" it by not telling you the order in which you should pass the objects to Assoc
Of course, since there is no discoverability of associations, all good garbage collection optimizations are kept.
With association objects, I think we can have a uniform base to play with objects without having to invent new sorts of objects (WeakMaps, Sets, Maps, etc.) while keeping the possibility to implement (hopefully as efficiently) these abstractions.
I'm not sure we should give up on private names though. They provide a very efficient syntax for private accessors. They could be considered as syntactic sugar of the Assoc API.
Moreover, I think we need a way to create unforgeable tokens which would be clearer than "Object.freeze({})" or "name.create()". This is very verbose to represent the creation of a token. What about some grawlix-y syntax for that? {*}
We are not "giv[ing] up on private names" -- you are really just talking a bit too autodidactically here, frankly.
Private names and weak maps are observably different in ES.next as proposed. One example: proxies, where accesses of private-named properties trap with a public-name counterpart substituted for the private name object key or identifier.
In implementation terms, and generalizing freely, weak maps take more GC time than private names. This is not (yet) a negligable issue.
In ES.next you can set a writable property named by a private name object key to a new value even if the containing object is frozen. Same for a property implemented by transposed set of a weak map indexed by the containing object (the map reference is the analogue of the property private-name-object key).
However: you cannot extend a non-extensible containing object by adding a private-named property. With weak maps, on the other hand, you may freely "add" weak map entries indexed by frozen objects.
So private name objects and weak maps differ observably and in important implementation economics. Both have their uses. You can't get rid of one with the other, not as proposed and not without complexities we're not going to take on.
Le 16/12/2011 18:18, Brendan Eich a écrit :
We are not "giv[ing] up on private names" -- you are really just talking a bit too autodidactically here, frankly.
Indeed. What I meant was "I do not mean that private names should be given up". French does not have a passive form (or at least it's rarely used) and a generic "we" ("on") is often used instead. Sorry for the confusion.
Private names and weak maps are observably different in ES.next as proposed. One example: proxies, where accesses of private-named properties trap with a public-name counterpart substituted for the private name object key or identifier.
I agree, this is a major difference and I don't see any way to reconciliate with weak maps.
However, I have questions and concerns about the public counterpart. Why does it exists? (there are very few words on it on the wiki and no link to previous discussion) Is it only to "prevents unintended leakage of the private name [to proxies]" as said in the wiki or is there another use case?
It seems to me that the current design of private names prevent easy composition of proxies. Let's say we have two attenuators (proxies which reduce your authority over a given object) constructors we'd like to compose: makeAttenuated1 and makeAttenuated2
// assuming we have an object 'o' and a private name 'p' var aao = makeAttenuated2(makeAttenuated1(o)); aao[p] = 37;
The 'set' trap of the attenuator2 is called with p.public as name. Then, this attenuator cannot pass p.public to the attenuator1 proxy as a name since p.public is not a name object (for good reasons). Should it create an internal dictionnary between public names and associate new private names to pass the to second attenuator? It seems that this doesn't scale very well.
Also, what if I actually want to give the private name to the proxy? Imagine I use 2 private names on a target, I hand you only the proxy and one private name (I don't share the other because I don't trust you enough). You can't unseal the secret unless I share with you an additional secret which is a public -> private dictionary.
A dictionary sounds costly to create each time I create a proxy. Also, since I have limited trust in you, I kept with me the second private name, so there was no "unintended leakage".
I think it would be interesting to have further explanations about "When proxy traps are invoked for a private name, they receive the name’s |.public| property instead of the name itself. This prevents unintended leakage of the private name, but still identifies the name to code that already has access to it." As I said in the original e-mail, a private name does not carry any authority as long as the entity who has it does not have a reference to any object you have hidden something with this name. It does not seem to be that big of a deal to give a private name to a proxy as long as you don't give the second secret.
And if you do not trust an object, just recreate a name. It sounds cheaper than when you need to create and pass dictionnaries whenever you need a limited trusted party to get some work done.
Is there a use case requiring the .public part?
In implementation terms, and generalizing freely, weak maps take more GC time than private names. This is not (yet) a negligable issue.
I'll make a pretentious claim which is: weak maps cannot be more expensive than private names. If private names are cheaper, you can always (internally) implement weakmaps as:
function WeakMap(){ var n = new Name();
return {
get: function(o){
return o[n];
},
set: function(o, val){
o[n] = val;
},
has: function(o){
return n in o;
},
delete: function(o){
delete o[n];
}
}
}
WeakMap = function(){ var n = new Name();
return {
get: function(o){
return o[n]
}
set: function(o, val){
o[n] = val;
}
}
}
WeakMaps are currently probably more expensive because they are implemented "as WeakMaps" (and because apparently, SpiderMonkey's garbage collector has some quadratic behaviors [1]).
If it's cheaper to add a private property to the objects, why not implement weak maps this way? It works the other way too: if WeakMaps are cheaper for other engines, implement your private names as weak maps!
In ES.next you can set a writable property named by a private name object key to a new value even if the containing object is frozen. Same for a property implemented by transposed set of a weak map indexed by the containing object (the map reference is the analogue of the property private-name-object key).
However: you cannot extend a non-extensible containing object by adding a private-named property. With weak maps, on the other hand, you may freely "add" weak map entries indexed by frozen objects.
I agree that the intentions of weak map and private names are different, but it would be pretty easy to override WeakMap.prototype.set to forbid the use of frozen (or whatever filtering rule you wish) objects as keys. If monkey patching is not beautiful, it's always possible to create an abstraction on top of WeakMaps for this exclusing feature.
I agree that the intentions are of both features different, but for this particular example it is a cosmetic difference since the limitation on private names could be emulated on weak maps or an abstraction on top of it.
So private name objects and weak maps differ observably
I think the only strong difference is the .public and I'd like to reopen the debate on this feature since it seems to prevent legitimate use cases especially under the light that now, the "forwarding proxy" pattern is first-class and it seems natural to share private names with parties I trust (especially if I have created the proxy myself).
and in important implementation economics.
I'm sorry, but I have to ask for a proof of this. I have polyfilled WeakMaps using private names in 10 lines of JS (engine internals go over the "don't add properties to frozen object" constraints which I can't in JavaScript). Whichever is cheaper for any engine could be used as a base implementation for both features. I don't see how both feature "differ in important implementation economics". They only do in the choices you made.
David
On Dec 16, 2011, at 11:52 AM, David Bruant wrote:
However, I have questions and concerns about the public counterpart. Why does it exists?
It exists to prevent unintentionally leaking private names. When you have a private name and you do a property get of another object, if that object doesn't know about that private name, it can't do anything with it. With proxies, simply looking up a private name on an object is implicitly giving away access to the object you're doing the lookup on. This means anyone could intercept a private name by handing a proxy to code that's doing a lookup of a private-named key.
(there are very few words on it on the wiki and no link to previous discussion) Is it only to "prevents unintended leakage of the private name [to proxies]" as said in the wiki or is there another use case?
It's critical for protecting the actual privacy of the names.
Le 16/12/2011 20:58, Brendan Eich a écrit :
My answer is pretty much the same than the previous message
Le 16/12/2011 21:03, David Herman a écrit :
On Dec 16, 2011, at 11:52 AM, David Bruant wrote:
However, I have questions and concerns about the public counterpart. Why does it exists? It exists to prevent unintentionally leaking private names. When you have a private name and you do a property get of another object, if that object doesn't know about that private name, it can't do anything with it. With proxies, simply looking up a private name on an object is implicitly giving away access to the object you're doing the lookup on. This means anyone could intercept a private name by handing a proxy to code that's doing a lookup of a private-named key.
Likewise, what prevents you from unintentionally leaking the public ->
private dictionary. You could hand a reference to it to a malicious party and you would be in equivalent trouble.
(there are very few words on it on the wiki and no link to previous discussion) Is it only to "prevents unintended leakage of the private name [to proxies]" as said in the wiki or is there another use case? It's critical for protecting the actual privacy of the names.
My understanding of object capabilities is that the only thing that is critical to protecting a secret is not sharing it. If you're not sure you trust an object, create a new name, or do whatever it takes to not share your secret but collaborate with this object. And either this object needs your secret to collaborate and it's just a malware or it can do its work without your secret and you probably should choose this way.
I agree that "o[name]" is easier to write than "o.shareDictionary(mySecretDictionary)", but it does not make it more dangerous from a security perspective. As I said in the other e-mail, we can discuss probabilities, likeliness of programmer mistakes, but then, we've moved away from the object capability field.
From: "David Bruant" <bruant.d at gmail.com>
** if I trust a proxy, I share a:
Stop right there. You don't know whether another object passed to you is a proxy or not. You cannot tell, in general. Nor should you have to care! Requiring you to care in order to avoid leaking private names is incredibly error-prone.
Again, private-named properties are "in" and "own", so proxies' handler traps should see something corresponding to them as property names. But leaks must be ruled out by design.
You can think of the p.public idea as analogus to public key encryption. By itself the (unique identity of the frozen) public key object discloses nothing and is not useful. Only if the proxy can access the private name and map from its .public object reference back to the private name can the proxy "decrypt".
Le 16/12/2011 22:03, Brendan Eich a écrit :
From: "David Bruant" <bruant.d at gmail.com>
** if I trust a proxy, I share a: Stop right there. You don't know whether another object passed to you is a proxy or not. You cannot tell, in general. Nor should you have to care!
I made a mistake when I talked about "trusting a proxy". It's not about trusting a proxy. It's about trusting an object, object which, in the "worst" case is a proxy, hence my use of "proxy". While I agree you should not have to worry which object is a proxy and which is a regular object, being aware of which objects you trust and which you don't is an exercice you already do when you program in an ocap style. That's my understanding at least.
Requiring you to care in order to avoid leaking private names is incredibly error-prone.
So is requiring you to care in order to avoid leaking the public ->
private map. It's no more, no less dangerous or error-prone. If I have to be careful about who I hand a reference to the public ->
private dictionary, how is it more dangerous or error-prone to be careful about who you hand the private name to? It's the exact same work.
I think the difference lies in the habit we have in ES5 that "o[n]" would not disclose a secret. Also, in ES5+private names "o[n]" does not disclose a secret. My impression is that '.public' in an attempt to preserve this property with proxies.
I propose to take the other approach which is to accept that "o[n]" may disclose 'n' if 'o' is a proxy. I agree this is a big change in how we think objects.
Again, private-named properties are "in" and "own", so proxies' handler traps should see something corresponding to them as property names. But leaks must be ruled out by design.
In some of his presentations, Mark Miller lists 4 ways for an object to have access to another object: introduction, parenthood, endowment and initial conditions. If 'o' is a proxy and if "o[n]" shared 'n' to 'o', then it would just be a case of introduction. You decide to hand a reference of the private name to 'o'. It doesn't look like the usual case of introduction (passing as function argument), but that would be another instance.
Maybe we just need to change the way we think of objects and get used to consider "o[n]" is an introduction. As much as "o.doWhatYouWant(n)".
You call sharing 'n' in "o[n]" a "leak", I call it a feature and like all features, we should be aware of potential threats. For sure it will affect as we program... equivalently to like defining a public ->
private map and give access to it will affect as we program. No more no less.
You can think of the p.public idea as analogus to public key encryption. By itself the (unique identity of the frozen) public key object discloses nothing and is not useful. Only if the proxy can access the private name and map from its .public object reference back to the private name can the proxy "decrypt".
I fully understand and agree with this paragraph and I claim that there is no more security in the additional "mapping" than there is in passing directly the private name to the proxy.
On Fri, Dec 16, 2011 at 6:09 PM, David Bruant <bruant.d at gmail.com> wrote:
Requiring you to care in order to avoid leaking private names is incredibly error-prone. So is requiring you to care in order to avoid leaking the public -> private map. It's no more, no less dangerous or error-prone. If I have to be careful about who I hand a reference to the public -> private dictionary, how is it more dangerous or error-prone to be careful about who you hand the private name to? It's the exact same work.
I think the difference lies in the habit we have in ES5 that "o[n]" would not disclose a secret. Also, in ES5+private names "o[n]" does not disclose a secret. My impression is that '.public' in an attempt to preserve this property with proxies.
I propose to take the other approach which is to accept that "o[n]" may disclose 'n' if 'o' is a proxy. I agree this is a big change in how we think objects.
I used to think this way about private names and proxies, and argued for this position. However, I was wrong and it's a bad idea. Here's why:
You might want to implement an object with a private field and a public method like this:
let o = (function () { let n = new Name(); return { [n]: 0; get: function() { return this[n]; }}})();
But, if we don't censor private names in proxies, this abstraction leaks!
Consider:
let p = new Proxy(...); o.get.call(p);
Now the name is leaked to the proxy. And there's no way you can fix this, without resorting to some other method of hiding things like closures.
The problem you raise, that ocap-style security relies on not leaking data ever, is, I think, a real one. But it's one that this solution for proxies mitigates, because almost all the time, you don't ever want to care about keeping track of which names have which public version, as in the example I gave. You only need this is you want to allow proxies to support your private name, which isn't the case for every abstraction. But it is the case that without the censoring, proxies break almost every abstraction built using private names.
Thanks, Sam -- couldn't have said it better.
Le 17/12/2011 00:24, Sam Tobin-Hochstadt a écrit :
On Fri, Dec 16, 2011 at 6:09 PM, David Bruant <bruant.d at gmail.com> wrote:
Requiring you to care in order to avoid leaking private names is incredibly error-prone. So is requiring you to care in order to avoid leaking the public -> private map. It's no more, no less dangerous or error-prone. If I have to be careful about who I hand a reference to the public -> private dictionary, how is it more dangerous or error-prone to be careful about who you hand the private name to? It's the exact same work.
I think the difference lies in the habit we have in ES5 that "o[n]" would not disclose a secret. Also, in ES5+private names "o[n]" does not disclose a secret. My impression is that '.public' in an attempt to preserve this property with proxies.
I propose to take the other approach which is to accept that "o[n]" may disclose 'n' if 'o' is a proxy. I agree this is a big change in how we think objects. I used to think this way about private names and proxies, and argued for this position. However, I was wrong and it's a bad idea. Here's why:
You might want to implement an object with a private field and a public method like this:
let o = (function () { let n = new Name(); return { [n]: 0; get: function() { return this[n]; }}})();
I propose to accept that "o[n]" may disclose 'n' if 'o' is a proxy. So, assuming it is accepted, then "this[n]" may be a form of disclosure depending on the value of |this|. I propose to accept it. (One good news is that mostly static analysis can assist us in finding where we may disclose 'n' in this scenario).
But, if we don't censor private names in proxies, this abstraction leaks!
I agree, but this is not inherent to private names, it is inherent to how you wrote your example. In a world where "o[n]" may disclose 'n', exposing a method with the instruction "this[n]" is a dangerous thing to write, indeed. It is as dangerous as writing "this.foo(n)". No more no less.
Consider:
let p = new Proxy(...); o.get.call(p);
In the case "get:function(){this.foo(n)}", consider: o.get.call(p);
the 'get' trap of the proxy is called with 'foo' as name, the proxy decides to create a function. This function gets called with 'n' as argument. 'n' is disclosed.
Now the name is leaked to the proxy.
In my case too and it is as unacceptable, but in no way it is more or less dangerous.
And there's no way you can fix this, without resorting to some other method of hiding things like closures.
I disagree, there is. In the case where you want the proxy ("worst case", here "proxy" means "untrusted third party") to have access to the private name, you need the proxy to have access to a public->private map. And this is the way
you have hidden your private name. Your private name is not hidden thanks to the language, but thanks to the fact that you do not provide the public->private map.
In the end, your security does not rely on the private name not being disclosed. It relies on your public -> private map not being disclosed.
The problem remains the same, but shifted somewhere else.
The problem you raise, that ocap-style security relies on not leaking data ever, is, I think, a real one. But it's one that this solution for proxies mitigates, because almost all the time,
I'm sorry, but regarding features that are not deployed yet, I cannot agree with a sentence with "almost all the time".
you don't ever want to care about keeping track of which names have which public version, as in the example I gave.
I have been written proxy abstractions where passing private names would be handy:
-
makeReadOnly(o) Creates a proxy that wraps o to make a read-only view of it. Passing private names transparently would be handy to make the abstraction work with private names. But of course, public->private dictionaries work too.
-
makeRecorded(o) Creates a proxy that record every change made to 'o'. Recording which change refers to which private name is part of it too. The goal is to be able to replay the different states o went through.
-
makeBranch(o) Creates an object that takes on it all changes 'o' should go through. Changes related to private names are part of it too.
In all this cases, if I create a proxy and someone else has a private name, everything should go fine with a simple dictionaries. But I'm sure that if we start having different actors, some with references to o, some with only the proxy, some with both, some with private names applied to either o or the proxy, we will end up with things that are not possible to do or ridiculously complicated when it comes to sharing authority (at the risk of disclosing the map to the wrong parties).
You only need this is you want to allow proxies to support your private name, which isn't the case for every abstraction. But it is the case that without the censoring, proxies break almost every abstraction built using private names.
Proxies and private names are not deployed in any platform, so maybe without censoring, proxies break abstraction, but as of December 17th 2011, this is a backward compatibility issue only with undeployed and untested code. I think there is time to get people used to the idea that "o[n]" and "this[n]" may be an act of disclosure.
Deciding to not censor does not break anything except the abstractions people who are aware of proxies and private names have /thought/ of.
If each time I create an abstraction using proxies, I need a public->private map, it is likely that I (and it's obviously not about "me", but anyone writing proxies) won't use private names with proxies. I may just use WeakMaps. What I need is a (object, some unforgeable object) mapping inside the proxy. If the cost of using private names is private+public+map, I can achieve what I want more efficiently with weakmaps and sharing a single (unforgeable) weakmap reference among trusted parties.
On Dec 17, 2011, at 4:04 AM, David Bruant wrote:
I propose to accept that "o[n]" may disclose 'n' if 'o' is a proxy.
This is just a non-starter. Then you can't use private names as private names.
(One good news is that mostly static analysis can assist us in finding where we may disclose 'n' in this scenario).
When you say "static analysis can solve this" you're saying "some hypothetical approximation to an undecidable problem will find out all the places where the unstated and unwritten intentions of the programmer were violated. Also, despite the fact that it's mathematically impossible for such a tool to have both no false positives and no false negatives, it will still be both safe and useful." I'll believe it when I see it?
Besides, we shouldn't knowingly add features that will invite bugs that can only be found with (as yet unspecified and non-existent) additional development tools.
But, if we don't censor private names in proxies, this abstraction leaks! I agree, but this is not inherent to private names, it is inherent to how you wrote your example. In a world where "o[n]" may disclose 'n', exposing a method with the instruction "this[n]" is a dangerous thing to write, indeed. It is as dangerous as writing "this.foo(n)". No more no less.
One of the important use cases of private names is the ability to create private data that collaborators can share. More specifically, two of the most common collaborators in JS are constructor functions and prototype methods. If you make it so that this[n] is no longer safe, then constructors and prototype methods can no longer use private names to collaborate with private properties.
And there's no way you can fix this, without resorting to some other method of hiding things like closures. I disagree, there is. In the case where you want the proxy ("worst case", here "proxy" means "untrusted third party") to have access to the private name, you need the proxy to have access to a public->private map.
You seem to be treating ES6 as being The Ultimate Proxy Language (with some additional side features). Proxies are not the common use case we are optimizing for, nor should they be.
The common case we are optimizing for has nothing to do with proxies. It's simple stuff like:
let n = Name();
function Stuff() {
this[n] = /* some internal stuff */;
}
Stuff.prototype = {
doStuff: function() {
... this[n] ...
}
};
If each time I create an abstraction using proxies, I need a public->private map, it is likely that I (and it's obviously not about "me", but anyone writing proxies) won't use private names with proxies. I may just use WeakMaps.
Great -- do that! If proxy writers have to work harder to deal with the public/private map, so be it! I would much rather tax proxy writers than tax everyone else. As cool as proxies are, they are not intended to be nearly as common as the usual simple functions + prototypes abstractions of JS.
Le 17/12/2011 22:37, David Herman a écrit :
On Dec 17, 2011, at 4:04 AM, David Bruant wrote:
I propose to accept that "o[n]" may disclose 'n' if 'o' is a proxy. This is just a non-starter. Then you can't use private names as private names.
I have read a lot of code that I have written myself and I admit that if I wanted to switch to private names, I would make myself vulnerable to the attacks mentionned and would be required to use closures anyway, so as you say, I wouldn't be able to use private names as private names.
(One good news is that mostly static analysis can assist us in finding where we may disclose 'n' in this scenario). When you say "static analysis can solve this" you're saying "some hypothetical approximation to an undecidable problem will find out all the places where the unstated and unwritten intentions of the programmer were violated. Also, despite the fact that it's mathematically impossible for such a tool to have both no false positives and no false negatives, it will still be both safe and useful." I'll believe it when I see it?
I was careful to say "assist" and not "solve".
Besides, we shouldn't knowingly add features that will invite bugs that can only be found with (as yet unspecified and non-existent) additional development tools.
I agree.
And there's no way you can fix this, without resorting to some other method of hiding things like closures. I disagree, there is. In the case where you want the proxy ("worst case", here "proxy" means "untrusted third party") to have access to the private name, you need the proxy to have access to a public->private map. You seem to be treating ES6 as being The Ultimate Proxy Language (with some additional side features).
I do not. I'm aware that proxies have benefits and positive things can get out of them. As it turns out, they are heavily used in Firefox as well from what I heard. It would be interesting to hear from the people who have experience with them to know how they feel about private names and the ".public"
Proxies are not the common use case we are optimizing for, nor should they be.
The common case we are optimizing for has nothing to do with proxies. It's simple stuff like:
let n = Name(); function Stuff() { this[n] = /* some internal stuff */; } Stuff.prototype = { doStuff: function() { ... this[n] ... } };
If each time I create an abstraction using proxies, I need a public->private map, it is likely that I (and it's obviously not about "me", but anyone writing proxies) won't use private names with proxies. I may just use WeakMaps. Great -- do that! If proxy writers have to work harder to deal with the public/private map, so be it! I would much rather tax proxy writers than tax everyone else. As cool as proxies are, they are not intended to be nearly as common as the usual simple functions + prototypes abstractions of JS.
Have you had a look at my alternative proposal [1]? It seems to solve all concerns raised by anyone (including me) in this thread.
David
With the new direct proxies API, there is potentially another solution to combining proxies with private names:
private name property get/set could bypass the proxy entirely and be forwarded unconditionally to the target, just like |typeof|, |Object.getPrototypeOf| and [[Class]].
Pro:
- no leakage of private names, no need for .public conversion
- works when composing proxies Con:
- proxies can no longer intercept private names even if they were allowed to
Independent of the above, this discussion does lead me to think that we should at least consider separate traps for getting/setting private names. The current proposal abuses the name parameter of the get trap (of type String) to encode private names. But can an implementation guarantee that ToString(privateName.public) does not correspond to any other user-generated String? If not, there is potential for confusion. Separate getPrivate/setPrivate traps would avoid this confusion:
var n = Name(); proxy[n] // triggers handler.getPrivate(target, n.public); proxy[n.public] // triggers handler.get(target, ToString(n.public));
Under the current proposal, both of the above would trigger the get trap. I think this could actually confuse a proxy that has a public->private
mapping for n to disclose the private name value to an attacker that was able to forge the string ToString(n.public). The attacker simply has to call proxy[forgedPublicName]. Am I missing something?
In any case, by separating private name access out into separate traps, if a proxy writer only implements get/set but not getPrivate/setPrivate, then private name access would just be forwarded to the proxy target by default, and there would be no need for the proxy to maintain a public->private
mapping for private names it does not know or care about.
Cheers, Tom
2011/12/17 David Bruant <bruant.d at gmail.com>
2011/12/19 Tom Van Cutsem <tomvc.be at gmail.com>
In any case, by separating private name access out into separate traps, if a proxy writer only implements get/set but not getPrivate/setPrivate, then private name access would just be forwarded to the proxy target by default, and there would be no need for the proxy to maintain a public->private mapping for private names it does not know or care about.
To clarify: the proxy logic I was thinking of is along the following lines:
// in the VM, when intercepting proxy[privateName]: var trap = handler.getPrivate; if (typeof trap === "function") { return trap.call(handler, target, privateName.public); // note: no ToString on privateName.public } else { return target[privateName]; // note: not target[privateName.public] !! }
It's that else-branch that makes this work out: if the proxy doesn't implement the getPrivate trap, private names will continue to work just fine. With the .public conversion, any proxy that does not special-case private names in its get trap will be broken: it'll forward the wrong property name.
Hello,
I see error here. Unless I misunderstood something, in present state it so that:
var n = Name(); proxy[n] // triggers handler.get(target, n.public); proxy[n.public] // triggers handler.get(target, ToString(n.public));
and n.public is object, not string. So get handler can easily discriminate between string and public object. Yes I can forge it with {toString: function() { return "foo"; }}, but you can simply check n.public === name to see if the right public for the name was supplied (and if you don't know n, do not meddle with its affairs, it's none of your business), so I see no need for special trap, reasonably coded get cannot be fooled by calling proxy[n.public].
As for:
private name property get/set could bypass the proxy entirely and be forwarded unconditionally to the target, just like |typeof|, |Object.getPrototypeOf| and [[Class]].
please look at "Forward proxies with private names" thread. There I propose something in this line, but less strict and more powerful while still secure.
Herby
-----Pôvodná správa---
2011/12/19 Herby Vojčík <herby at mailbox.sk>
Hello,
I see error here. Unless I misunderstood something, in present state it so that:
var n = Name(); proxy[n] // triggers handler.get(target, n.public);
proxy[n.public] // triggers handler.get(target, ToString(n.public));
and n.public is object, not string. So get handler can easily discriminate between string and public object. Yes I can forge it with {toString: function() { return "foo"; }}, but you can simply check n.public === name to see if the right public for the name was supplied (and if you don't know n, do not meddle with its affairs, it's none of your business), so I see no need for special trap, reasonably coded get cannot be fooled by calling proxy[n.public].
Two points:
-
I was under the impression that in the present proposal, what is passed to the "get" trap is really ToString(name.public), not the name.public object. The proposal is vague in this regard, but I'm pretty sure Sam and Dave did not intend to widen the type of the "name" parameter of the "get" trap from String to Object. Sam, Dave, can you clarify?
-
Even if name.public would not be stringified, if both normal and private name property access would go through the "get" trap, it would still be the responsibility of the "get" trap to properly forward private names. If this is the case, a very common bug with proxies will be that the "get" trap will mistakenly turn private name access into normal property access of the wrong property name.
What my proposal boils down to is to consider obj[privateName] and obj[string] as two separate operations, with their own traps. This allows us to get the default behavior for private names right (forward as target[privateName], not as target[privateName.public]).
As for:
private name property get/set could bypass the proxy entirely and be forwarded unconditionally to the target, just like |typeof|, |Object.getPrototypeOf| and [[Class]].
please look at "Forward proxies with private names" thread. There I propose something in this line, but less strict and more powerful while still secure.
Yes, I just read your proposal (sorry for missing it earlier).
Calling the trap and catching a special exception is something that we have avoided in the proxy design thus far. One possibility is to have the "getPrivate" trap return a boolean. If the trap returns false, the proxy will forward the private name access. This would allow the getPrivate trap to check whether the "public" argument corresponds to one of its own private names. If not, it just returns false (no throwing exceptions or calling special Proxy.getDefault methods needed).
Le 19/12/2011 11:03, Tom Van Cutsem a écrit :
With the new direct proxies API, there is potentially another solution to combining proxies with private names:
private name property get/set could bypass the proxy entirely and be forwarded unconditionally to the target, just like |typeof|, |Object.getPrototypeOf| and [[Class]].
Pro:
- no leakage of private names, no need for .public conversion
- works when composing proxies Con:
- proxies can no longer intercept private names even if they were allowed to
If private name bypass the proxy entirely, what will we have to invent to intercept private names? ;-) There are use cases for intercepting private names (either directly or indirectly).
Independent of the above, this discussion does lead me to think that we should at least consider separate traps for getting/setting private names. The current proposal abuses the name parameter of the get trap (of type String) to encode private names. But can an implementation guarantee that ToString(privateName.public) does not correspond to any other user-generated String? If not, there is potential for confusion. Separate getPrivate/setPrivate traps would avoid this confusion:
var n = Name(); proxy[n] // triggers handler.getPrivate(target, n.public); proxy[n.public] // triggers handler.get(target, ToString(n.public));
Under the current proposal, both of the above would trigger the get trap. I think this could actually confuse a proxy that has a public->private mapping for n to disclose the private name value to an attacker that was able to forge the string ToString(n.public). The attacker simply has to call proxy[forgedPublicName]. Am I missing something?
The public part is an unforgeable object (with the same .toString than the private name), so there is no ambiguity in a public->private map.
You use the unforgeable public object as key in the map, so no collision issue due to string forgeability.
In any case, by separating private name access out into separate traps, if a proxy writer only implements get/set but not getPrivate/setPrivate, then private name access would just be forwarded to the proxy target by default, and there would be no need for the proxy to maintain a public->private mapping for private names it does not know or care about.
If you define a getPrivate/setPrivate trap, does it have access to the private name directly or its public counterpart? In the former case, this cannot be implemented in JavaScript (unless the private name is passed directly) and the stealth threat remains, in the latter case, I'm not sure I see the benefit over the current design.
Le 19/12/2011 11:03, Tom Van Cutsem a écrit :
(...) Separate getPrivate/setPrivate traps would avoid this confusion: (...)
One thing that is unsaid in the current proposal is whether private names can be used with Object.defineProperty and Object.getOwnPropertyDescriptor. I think that private getters and setters would be awesome. But it would mean that the "private handler API" would need to be extend to 'definePrivateProperty', 'getOwnPrivatePropertyDescriptor' (and 'deletePrivate' as well)
2011/12/19 Tom Van Cutsem <tomvc.be at gmail.com>
Calling the trap and catching a special exception is something that we have avoided in the proxy design thus far. One possibility is to have the "getPrivate" trap return a boolean. If the trap returns false, the proxy will forward the private name access. This would allow the getPrivate trap to check whether the "public" argument corresponds to one of its own private names. If not, it just returns false (no throwing exceptions or calling special Proxy.getDefault methods needed).
Sorry, this doesn't work. The getPrivate trap already uses its return value to indicate the value of the private name. So yes, if the getPrivate trap wants to trigger the "default behavior" it either has to throw a special exception or return a special unique value to indicate this. It's an interesting difference with all other traps, which can just perform the default forwarding behavior by calling one of the Reflect.* methods. Obviously, we can't provide a Reflect.getPrivate(target, public) since that breaks the entire point of the private/public distinction.
2011/12/19 David Bruant <bruant.d at gmail.com>
The public part is an unforgeable object (with the same .toString than the private name), so there is no ambiguity in a public->private map. You use the unforgeable public object as key in the map, so no collision issue due to string forgeability.
Again, the private names proposal is vague in this respect. I am under the impression that it passes ToString(privateName.public) to the "get" trap.
In any case, by separating private name access out into separate traps, if a proxy writer only implements get/set but not getPrivate/setPrivate, then private name access would just be forwarded to the proxy target by default, and there would be no need for the proxy to maintain a public->private mapping for private names it does not know or care about. If you define a getPrivate/setPrivate trap, does it have access to the private name directly or its public counterpart?
Only to the public counterpart, of course, otherwise we're back to square 1 and private names would leak.
2011/12/19 David Bruant <bruant.d at gmail.com>
Le 19/12/2011 11:03, Tom Van Cutsem a écrit :
(...) Separate getPrivate/setPrivate traps would avoid this confusion: (...) One thing that is unsaid in the current proposal is whether private names can be used with Object.defineProperty and Object.getOwnPropertyDescriptor. I think that private getters and setters would be awesome. But it would mean that the "private handler API" would need to be extend to 'definePrivateProperty', 'getOwnPrivatePropertyDescriptor' (and 'deletePrivate' as well)
Good point. That is indeed a major drawback of separating out all private name handling in parallel traps.
I think I don't have a problem with widening the type of the name parameter of all of these traps from String to Object. Then a proxy handler can at least test whether it is intercepting a private name or a normal property. The only thing I'm worried about at this point is that there has to be a simple way for the proxy to say "I'm not interested in intercepting this private name, just forward it to my target". Otherwise I'm afraid most proxies will wrongly forward a private name access as a normal property access.
Hello,
I'd say this is not specialty of get trap. If you don’t know the private name itself (and the scenario where you don’t know it but you want to forward it is the interesting scenario), you cannot use any API since you simply don’t have the name which you could pass. If nothing more, this is true for set handler as well (and delete, too, ..., any one that has to deal with private name).
Isn’t it?
Herby
-----Pôvodná správa---
2011/12/19 Herby Vojčík <herby at mailbox.sk>
I'd say this is not specialty of get trap. If you don’t know the private name itself (and the scenario where you don’t know it but you want to forward it is the interesting scenario), you cannot use any API since you simply don’t have the name which you could pass. If nothing more, this is true for set handler as well (and delete, too, ..., any one that has to deal with private name).
Isn’t it?
It is.
On 17 December 2011 00:24, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
I used to think this way about private names and proxies, and argued for this position. However, I was wrong and it's a bad idea. Here's why:
You might want to implement an object with a private field and a public method like this:
let o = (function () { let n = new Name(); return { [n]: 0; get: function() { return this[n]; }}})();
But, if we don't censor private names in proxies, this abstraction leaks!
Consider:
let p = new Proxy(...); o.get.call(p);
Now the name is leaked to the proxy. And there's no way you can fix this, without resorting to some other method of hiding things like closures.
Hm, isn't this example rather demonstrating that the ability to do self stealing -- i.e., the lack of lexical `this' -- is violating basic abstraction principles (as we all know)? That is, in JS you have to manually fix such abstractions using .bind on your methods (or potential nicer alternatives in ES6). Arguably, the problem is not proxies or private names (under David's semantics) per se.
But of course, we cannot really fix `this', so we introduce public name conversion (and other ad-hoc complications surrounding private names) as compatibility hacks that prevent blatant leaks for existing JS idioms. It's not puristic, but it is convenient and pragmatically safer.
Le 20/12/2011 14:45, Andreas Rossberg a écrit :
On 17 December 2011 00:24, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
I used to think this way about private names and proxies, and argued for this position. However, I was wrong and it's a bad idea. Here's why:
You might want to implement an object with a private field and a public method like this:
let o = (function () { let n = new Name(); return { [n]: 0; get: function() { return this[n]; }}})();
But, if we don't censor private names in proxies, this abstraction leaks!
Consider:
let p = new Proxy(...); o.get.call(p);
Now the name is leaked to the proxy. And there's no way you can fix this, without resorting to some other method of hiding things like closures. Hm, isn't this example rather demonstrating that the ability to do self stealing -- i.e., the lack of lexical `this' -- is violating basic abstraction principles (as we all know)?
This particular example used 'this', but similar examples may not.
let marker = (function(){ let n = new Name(); let counter = 0;
return {
mark: function(o){
o[n] = counter++;
}
readMark: function(o){
return o[n];
}
};
})();
marker.mark(maliciousProxy);
...and the name just leaked allowing a malicious proxy to mess with the marking.
That is, in JS you have to manually fix such abstractions using .bind on your methods (or potential nicer alternatives in ES6). Arguably, the problem is not proxies or private names (under David's semantics) per se.
But of course, we cannot really fix `this', so we introduce public name conversion (and other ad-hoc complications surrounding private names) as compatibility hacks that prevent blatant leaks for existing JS idioms. It's not puristic, but it is convenient and pragmatically safer.
Indeed. What do you think of the different alternative proposals (especially regarding the "ad-hoc complications" you point out)?
On 20 December 2011 21:06, David Bruant <bruant.d at gmail.com> wrote:
Le 20/12/2011 14:45, Andreas Rossberg a écrit :
On 17 December 2011 00:24, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
You might want to implement an object with a private field and a public method like this:
let o = (function () { let n = new Name(); return { [n]: 0; get: function() { return this[n]; }}})();
But, if we don't censor private names in proxies, this abstraction leaks!
Consider:
let p = new Proxy(...); o.get.call(p);
Now the name is leaked to the proxy. And there's no way you can fix this, without resorting to some other method of hiding things like closures. Hm, isn't this example rather demonstrating that the ability to do self stealing -- i.e., the lack of lexical `this' -- is violating basic abstraction principles (as we all know)? This particular example used 'this', but similar examples may not.
let marker = (function(){ let n = new Name(); let counter = 0;
return { mark: function(o){ o[n] = counter++; } readMark: function(o){ return o[n]; } }; })();
marker.mark(maliciousProxy);
...and the name just leaked allowing a malicious proxy to mess with the marking.
Sure, but o here is just a random argument, and you can never make any assumptions about what gets passed as an argument (unless you have some kind of nominal type or branding mechanism).
Whereas this' refers to the object on which the method itself sits, so intuitively its "own" object. Not so in JS, I know, but it's the very basis of standard OO-style abstraction/encapsulation. That is, without binding
this' (or avoiding it altogether) there is no
reliable way to build an object-oriented abstraction and ensure your
own invariants. Potential leakage of private names is just a symptom.
Indeed. What do you think of the different alternative proposals (especially regarding the "ad-hoc complications" you point out)?
I agree with your analysis that passing the public view of a private name to traps is not a true reification. However, I'm not sure I prefer the alternative you propose. Putting a random flag on names to control the behaviour of a completely different language feature seems awkward, and introduces undesirable conceptual coupling.
In essence, you are introducing two separate types of private names, but the distinction only is observable in situations that the implementer should not need to think about in the first place. What would be the guideline for picking the right one? When would I ever want to pick the weaker variant?
On 19 December 2011 16:56, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
I think I don't have a problem with widening the type of the name parameter of all of these traps from String to Object. Then a proxy handler can at least test whether it is intercepting a private name or a normal property.
Widening the "type" seems like the only coherent solution to me. In the presence of private names, a property name simply isn't a string anymore, but string|private_name. Consequently, on reflective boundaries, if you want to protect privacy, that turns into string|public_name.
The only thing I'm worried about at this point is that there has to be a simple way for the proxy to say "I'm not interested in intercepting this private name, just forward it to my target". Otherwise I'm afraid most proxies will wrongly forward a private name access as a normal property access.
Indeed, that looks like a problem.
I think your idea points in the right direction. In fact, there is no reason to make it specific to private names, it might be useful for public properties, too. Instead of simply having all-or nothing traps, and making it the responsibility of each trap to forward unwanted cases properly, allow traps to signal "I don't care, please forward" on a per-property basis.
Unfortunately, I don't have a good suggestion for a convenient interface, besides introducing a sentinel value that traps can return (I am still puzzled how people can survive in dignity without variants and tuples... :) ).
On Wed, Dec 21, 2011 at 5:25 AM, Andreas Rossberg <rossberg at google.com> wrote:
Hm, isn't this example rather demonstrating that the ability to do self stealing -- i.e., the lack of lexical `this' -- is violating basic abstraction principles (as we all know)? This particular example used 'this', but similar examples may not.
let marker = (function(){ let n = new Name(); let counter = 0;
return { mark: function(o){ o[n] = counter++; } readMark: function(o){ return o[n]; } }; })();
marker.mark(maliciousProxy);
...and the name just leaked allowing a malicious proxy to mess with the marking.
Sure, but o here is just a random argument, and you can never make any assumptions about what gets passed as an argument (unless you have some kind of nominal type or branding mechanism).
In the absence of proxies, David's example is actually safe, regardless of what object `o' is. And you really need David's example to work to make private names as useful as they can be -- otherwise, it's just re-enabling |private| from Java, and not adding anything more.
-----Pôvodná správa---
Le 21/12/2011 11:25, Andreas Rossberg a écrit :
Indeed. What do you think of the different alternative proposals (especially regarding the "ad-hoc complications" you point out)? I agree with your analysis that passing the public view of a private name to traps is not a true reification. However, I'm not sure I prefer the alternative you propose. Putting a random flag on names to control the behaviour of a completely different language feature seems awkward, and introduces undesirable conceptual coupling.
In essence, you are introducing two separate types of private names, but the distinction only is observable in situations that the implementer should not need to think about in the first place.
I don't know to what extent this is a receivable arguments. There are other instances of "the implementer should not need to think about in the first place" (I assume you meant "JavaScript programmer"). For instance, WeakMaps and Maps. As a programmer, I want an object -> value map. Why am I given 2
almost-similar features to do this? Because it's an undecidable problem to know whether or not the program is going to iterate over the keys, so 2 features are created. One without key enumeration and one with enumeration.
Same goes for proper tail calls. Currently no browser implements it which limitates the ability for some programmers to write JS programs in a given style. So they change how they write programs. But they shouldn't have to care (hence the current proposal regarding proper tail calls).
What would be the guideline for picking the right one? When would I ever want to pick the weaker variant?
You would pick the weaker variant whenever you would share a public->private name map under the current proposal (which is when you
want a proxy to have access to the private name). I really see this like WeakMaps and Maps. What would be the guideline for picking the right one? Understand the differences, understand the problem you want to solve and your choice is made.
On 21 December 2011 12:41, Sam Tobin-Hochstadt <samth at ccs.neu.edu> wrote:
On Wed, Dec 21, 2011 at 5:25 AM, Andreas Rossberg <rossberg at google.com> wrote:
Hm, isn't this example rather demonstrating that the ability to do self stealing -- i.e., the lack of lexical `this' -- is violating basic abstraction principles (as we all know)? This particular example used 'this', but similar examples may not.
let marker = (function(){ let n = new Name(); let counter = 0;
return { mark: function(o){ o[n] = counter++; } readMark: function(o){ return o[n]; } }; })();
marker.mark(maliciousProxy);
...and the name just leaked allowing a malicious proxy to mess with the marking.
Sure, but o here is just a random argument, and you can never make any assumptions about what gets passed as an argument (unless you have some kind of nominal type or branding mechanism).
In the absence of proxies, David's example is actually safe, regardless of what object `o' is. And you really need David's example to work to make private names as useful as they can be -- otherwise, it's just re-enabling |private| from Java, and not adding anything more.
In fact, I mainly view it like that. Any additional expressiveness is perhaps nice, but not essential. In particular, I don't consider the above example a good idea in real code, with or sans proxies. You shouldn't go off mutating arbitrary objects, and it won't work with non-extensible objects anyway (at least that's what I gather as the current state of discussion regarding private names and freezing). A map seems far more adequate for that.
Le 21/12/2011 13:47, Andreas Rossberg a écrit :
In particular, I don't consider the above example a good idea in real code, with or sans proxies. You shouldn't go off mutating arbitrary objects, and it won't work with non-extensible objects anyway (at least that's what I gather as the current state of discussion regarding private names and freezing). A map seems far more adequate for that.
Of course. The only purpose of this example was to show that an equivalent leak could occur without the specific use of 'this'. I don't know to what extent this code was interesting or if it could have been implemented more efficiently or more expressively.
On 21 December 2011 13:33, David Bruant <bruant.d at gmail.com> wrote:
Le 21/12/2011 11:25, Andreas Rossberg a écrit :
In essence, you are introducing two separate types of private names, but the distinction only is observable in situations that the implementer should not need to think about in the first place. I don't know to what extent this is a receivable arguments. There are other instances of "the implementer should not need to think about in the first place" (I assume you meant "JavaScript programmer"). For instance, WeakMaps and Maps. As a programmer, I want an object -> value map. Why am I given 2 almost-similar features to do this? Because it's an undecidable problem to know whether or not the program is going to iterate over the keys, so 2 features are created. One without key enumeration and one with enumeration.
I don't think these examples are comparable. In any case, every programming language, except the bare lambda calculus, has semantic redundancy. In JavaScript today, why are there functions with multiple arguments? You could as well pass an object. (And in fact, some languages work along these lines.)
There are pragmatic reasons for introducing redundancy. And the properties of weak maps vs private names are sufficiently different beyond a superficial abstract level to warrant the redundancy.
What would be the guideline for picking the right one? When would I ever want to pick the weaker variant? You would pick the weaker variant whenever you would share a public->private name map under the current proposal (which is when you want a proxy to have access to the private name).
OK, that's what I'd refer to as the "stronger" variant :) (in the sense of being somewhat safer wrt privacy). So when would you pick the other?
I really see this like WeakMaps and Maps. What would be the guideline for picking the right one? Understand the differences, understand the problem you want to solve and your choice is made.
Well, I'd argue that those guidelines are pretty well established regarding weak maps.
On 21 December 2011 13:52, David Bruant <bruant.d at gmail.com> wrote:
Le 21/12/2011 13:47, Andreas Rossberg a écrit :
In particular, I don't consider the above example a good idea in real code, with or sans proxies. You shouldn't go off mutating arbitrary objects, and it won't work with non-extensible objects anyway (at least that's what I gather as the current state of discussion regarding private names and freezing). A map seems far more adequate for that. Of course. The only purpose of this example was to show that an equivalent leak could occur without the specific use of 'this'. I don't know to what extent this code was interesting or if it could have been implemented more efficiently or more expressively.
Yes, I reckoned. I only pointed this out because I was assuming that Sam assumed (based on his reply) that you were thinking of it as an important example. :)
On 21 December 2011 14:02, Andreas Rossberg <rossberg at google.com> wrote:
Yes, I reckoned. I only pointed this out because I was assuming that Sam assumed (based on his reply) that you were thinking of it as an important example. :)
("important" in the sense of "practical")
Le 21/12/2011 13:59, Andreas Rossberg a écrit :
On 21 December 2011 13:33, David Bruant <bruant.d at gmail.com> wrote:
Le 21/12/2011 11:25, Andreas Rossberg a écrit :
In essence, you are introducing two separate types of private names, but the distinction only is observable in situations that the implementer should not need to think about in the first place. I don't know to what extent this is a receivable arguments. There are other instances of "the implementer should not need to think about in the first place" (I assume you meant "JavaScript programmer"). For instance, WeakMaps and Maps. As a programmer, I want an object -> value map. Why am I given 2 almost-similar features to do this? Because it's an undecidable problem to know whether or not the program is going to iterate over the keys, so 2 features are created. One without key enumeration and one with enumeration. I don't think these examples are comparable. In any case, every programming language, except the bare lambda calculus, has semantic redundancy. In JavaScript today, why are there functions with multiple arguments? You could as well pass an object. (And in fact, some languages work along these lines.)
There are pragmatic reasons for introducing redundancy.
That's what I was justifying too with the introduction of Maps and WeakMaps in the language. I think we agree on that, don't we?
And the properties of weak maps vs private names are sufficiently different beyond a superficial abstract level to warrant the redundancy.
In the part you're responding to, I was comparing Maps and WeakMaps, not WeakMaps and private names.
Regarding my initial message, even if the title suggests that 2 features could be "the same", I did not argue towards removing any of the 2. It was more of a reflection toward unifying a low-level component that could be used in both case.
What would be the guideline for picking the right one? When would I ever want to pick the weaker variant? You would pick the weaker variant whenever you would share a public->private name map under the current proposal (which is when you want a proxy to have access to the private name). OK, that's what I'd refer to as the "stronger" variant :) (in the sense of being somewhat safer wrt privacy). So when would you pick the other?
Sorry for the interpretation confusion. To answer more in detail to your question: With current proposals, as far as proxies are concerned, the secret is not that much the private name anymore. It rather is whether or not the proxy has a way to find the private name based on a public name. So, sharing or not a private name with a proxy requires an action from the private name holder, this action being to share a public->private
mapping mechanism with the proxy
Since an action is required from the private name holder to share the name, my suggestion is to make it more explicit with the boolean which in essence says "I want this private name to be shared with proxies" or "I don't want this private name to be shared with proxies". Current proposals need creating, maintaining and sharing a map to achieve this. Mine needs to choose the right boolean. In doubt or if you don't case, don't choose a boolean and "false" (the safer case) is chosen for you by default.
So, to answer your question, you choose your boolean value based on the same criteria than previously: based on whether or not you want your secret to be shared to proxy handlers. With current proposals, the implementation side of answering this question is deciding whether or not to create and share a map. And, of course, if you don't ask the question to yourself, you likely default to not creating a map and not sharing... like in my proposal.
Specifics are different, but the baseline between my proposal and the current one is the same: by default, don't share a private name with a proxy. I make the "I want to share with proxies" case a more straightforward. And the "I want to share with some proxies, but not some others" case remains complicated, which, I think, is fair.
2011/12/21 Herby Vojčík <herby at mailbox.sk>
From: Andreas Rossberg Unfortunately, I don't have a good suggestion for a convenient interface, besides introducing a sentinel value that traps can return (I am still puzzled how people can survive in dignity without variants and tuples... :) ).
I was proposing some solutionto exactly this in "Forward proxies with private names" thread. Not acceptable (especially the call, if exception is too hard to swallow)?
Two reasons against:
- Probably introduces a high overhead, since it implies every trap invocation needs to be wrapped in a try-block to catch the exception. Under no circumstances should such an exception unwind the stack any further, to avoid confusing other trap invocations on the stack.
- Misuse of exceptions: asking a proxy to "please forward" is not an exceptional situation. I was taught it is bad API design to abuse exceptions for non-exceptional situations.
The cleanest approach so far seems to be to either return a per-call unique sentinel value or a tuple (true, realReturnValue) or false. Both introduce extra per-call allocation, and add more complexity to an already complex API. I would not consider either to be a big improvement over proxies not being able to forward private names unknown to them.
Another idea: interpret "undefined" as the sentinel value for the "get" trap, but only when intercepting a private name. AFAICT, the only power we take away from a proxy p then is that for a private name n, it cannot state that p[n] is undefined while target[n] is not undefined.
By the way, it occurred to me that from a security perspective, it is odd that a proxy p can decide on the outcome of p[n] even if it does not know about n: a client of p might assume that p[n] is a "reliable" value, since it could only have been stored there by another party with knowledge of n, and "private names don't leak through proxies". Yet the proxy might fool its client into thinking it does know of n, since it can provide a non-undefined value for p[n]. That's an argument if favor of not allowing proxies to intercept private names ever, not even with .public conversion.
-----Pôvodná správa---
2011/12/21 Herby Vojčík <herby at mailbox.sk>
What about the second possibility that was there (calling a specific call to signal "please forward")? In my original proposal it would break execution of the trap immediately, but it is probably just an exception in disguise - it can be so that such a call sets a flag "please forward" and the trap function is allowed to run along until it does a return and after that return, the proxy machinery does the forwarding. No need to complicate return values by tuples, then, just set the flag and return.
I don't think that helps either:
- You still need to allocate a separate flag per call to deal with reentrancy.
- The ability to set a flag that will override a trap's return value, and still allowing the trap to return a value can be terribly confusing:
get: function() { ... f(); ... return v; }
Now, deep down in the call stack of f(), the flag is set. The value v returned from the get trap will be ignored. The non-locality of mutating a flag can lead to difficult to debug programs.
On 21 December 2011 20:42, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
2011/12/21 Herby Vojčík <herby at mailbox.sk>
From: Andreas Rossberg Unfortunately, I don't have a good suggestion for a convenient interface, besides introducing a sentinel value that traps can return (I am still puzzled how people can survive in dignity without variants and tuples... :) ).
I was proposing some solutionto exactly this in "Forward proxies with private names" thread. Not acceptable (especially the call, if exception is too hard to swallow)?
Two reasons against:
- Probably introduces a high overhead, since it implies every trap invocation needs to be wrapped in a try-block to catch the exception. Under no circumstances should such an exception unwind the stack any further, to avoid confusing other trap invocations on the stack.
- Misuse of exceptions: asking a proxy to "please forward" is not an exceptional situation. I was taught it is bad API design to abuse exceptions for non-exceptional situations.
I agree that exceptions are the wrong approach. Same goes for functions, especially since nothing prevents you from storing them and calling them out of context, which must be discovered and handled.
The cleanest approach so far seems to be to either return a per-call unique sentinel value or a tuple (true, realReturnValue) or false. Both introduce extra per-call allocation, and add more complexity to an already complex API.
Frankly, I don't see the necessity to have a fresh sentinel value per call. There is no serious issue with taking one designated, API-specific value out of the domain of proper values for that operation (especially considering that you just suggested using undefined, which certainly is more intrusive ;) ).
I would not consider either to be a big improvement over proxies not being able to forward private names unknown to them.
By the way, it occurred to me that from a security perspective, it is odd that a proxy p can decide on the outcome of p[n] even if it does not know about n: a client of p might assume that p[n] is a "reliable" value, since it could only have been stored there by another party with knowledge of n, and "private names don't leak through proxies". Yet the proxy might fool its client into thinking it does know of n, since it can provide a non-undefined value for p[n]. That's an argument if favor of not allowing proxies to intercept private names ever, not even with .public conversion.
I strongly believe that any ad-hoc behavioural distinction that we need to introduce between plain and private property names indicates a problem with the semantics of either private names or proxies (or JS objects in general, which is the scariest case). We should try very hard to avoid them.
Regarding your specific argument above, I'd argue that it is not actually odd. Any attempt to set private properties on objects you do not "own" is highly questionable to start with. Don't expect it to work. And in all other cases you should usually be able to set up their behaviour properly. That is, I currently don't see a valid use case for accessing a private property on a proxy unless you created the proxy yourself. (Though I just realised that Proxy.attach creates a huge issue there...)
I can also turn your argument around. Wouldn't it be much odder if I, as a proxy implementer, cannot keep others from setting unwanted properties on my own object, no matter what the name is?
2011/12/22 Andreas Rossberg <rossberg at google.com>
On 21 December 2011 20:42, Tom Van Cutsem <tomvc.be at gmail.com> wrote:
The cleanest approach so far seems to be to either return a per-call unique sentinel value or a tuple (true, realReturnValue) or false. Both introduce extra per-call allocation, and add more complexity to an already complex API.
Frankly, I don't see the necessity to have a fresh sentinel value per call. There is no serious issue with taking one designated, API-specific value out of the domain of proper values for that operation (especially considering that you just suggested using undefined, which certainly is more intrusive ;) ).
Agreed.
I would not consider either to be a big improvement over proxies not being able to forward private names unknown to them.
By the way, it occurred to me that from a security perspective, it is odd that a proxy p can decide on the outcome of p[n] even if it does not know about n: a client of p might assume that p[n] is a "reliable" value, since it could only have been stored there by another party with knowledge of n, and "private names don't leak through proxies". Yet the proxy might fool its client into thinking it does know of n, since it can provide a non-undefined value for p[n]. That's an argument if favor of not allowing proxies to intercept private names ever, not even with .public conversion.
I strongly believe that any ad-hoc behavioural distinction that we need to introduce between plain and private property names indicates a problem with the semantics of either private names or proxies (or JS objects in general, which is the scariest case). We should try very hard to avoid them.
Regarding your specific argument above, I'd argue that it is not actually odd. Any attempt to set private properties on objects you do not "own" is highly questionable to start with. Don't expect it to work.
Hmm, while I share your point of view, I don't think this fully aligns with the envisioned use of private names. One use case of private names is to enable more modular monkey-patching. For this use case, the whole point is to set private names on objects you don't own. However, the private names strawman page states that in those cases the privacy of the name doesn't matter as much as its uniqueness. So your point applies to the subset of use cases of private names where privacy is important.
And in all other cases you should usually be able to set up
their behaviour properly. That is, I currently don't see a valid use case for accessing a private property on a proxy unless you created the proxy yourself. (Though I just realised that Proxy.attach creates a huge issue there...)
I can also turn your argument around. Wouldn't it be much odder if I, as a proxy implementer, cannot keep others from setting unwanted properties on my own object, no matter what the name is?
Weak maps can achieve a similar relationship without the proxy intervening, but I agree that for private names it's more odd, since a private name is logically an own property of the object.
For the modular monkey-patching use-case, it seems reasonable to allow proxies to intercept such private names.
Le 22/12/2011 10:47, Andreas Rossberg a écrit :
I strongly believe that any ad-hoc behavioural distinction that we need to introduce between plain and private property names indicates a problem with the semantics of either private names or proxies (or JS objects in general, which is the scariest case). We should try very hard to avoid them.
I think that you are the one who pointed the actual problem [1] when explaining that is the root issue in Sam's use case is the lack of lexical 'this' (and neither private names nor proxies) Assuming this is an issue that cannot be fixed in ES6, we have to accept it and work around it. I think we are in such a case where we need to find a workaround. I agree we should try to avoid this kind of decision, but not being able to fix lexical 'this' does not seem to let other options than dividing private names in a way or another. Current proposal suggests public/private and implicit conversion when passing to traps. I proposed 2 kinds of private names. Tom did too (with a slightly different semantics).
I agree with you and wished we didn't have to do this, but it seems necessary, assuming the strong backward compatibility constraint we have.
David
I couldn't post on es-discuss yesterday, so I rewrote my message and posted it on github [1]. Here is the markdown source:
PART 1: The same feature?
When looked abstractly, both private name and weak maps propose an equivalent feature: a mapping between 2 unforgeable references and a value. Both allow to share a secret with someone assuming the knowledge of 2 unforgeable entities. Both have the interesting property of symmetric non-discoverability:
This can be used in WeakMaps to optimize garbage collection. As it turns out, the same thing stands for private names: If nothing holds a reference to a private name, all related slots in objects can be garbage-collected as well.
There are a couple of differences:
I claim that these can be reimplemented by overriding the WeakMap API.
I'm open to discussion on whether there are cases that can be implemented with one API and not the other. For the rest of this message, I'll assume that it's the same feature with 2 different syntax.
PART 2: A more generic syntax
Unfortunately, private names and WeakMaps (and maps and sets...) both define a special new sort of object to work. What about an API that would allow to associate any 2 objects and bind a "secret" value to the association?
// In one part of the code var o1 = {}, o2 = {}; var a = Assoc(o1, o2); a.set(37); // ... // In another component which has a reference to both o1 and o2 var myA = Assoc(o1, o2); myA.get(); // 37
In order to unseal a secret associated with 2 objects, you need a reference to both, that's it. Exactly like with WeakMaps, exactly like with private names.
Part 2.1: where I pretend I can reimplement WeakMaps with the Assoc API
WeakMap = function(){ var weakMapIdentity = {}; var presence = {}; return { get: function(o){ return Assoc(weakMapIdentity, o).get(); }, set: function(o, val){ Assoc(presence, o).set(true); return Assoc(weakMapIdentity, o).set(val); }, has: function(o){ return !!Assoc(presence, o).get(); }, delete = function(o){ Assoc(presence, o).set(undefined); } } }
Interestingly, 2 identities are required to emulate weakmaps. One for the values (weakMapIdentity) and one to consider presence of keys. In my opinion, the WeakMap API should be separated into 2 APIs. One with get/set and the other with add/remove/has which would find the natural name of WeakSet.
Part 2.2: where I go further and get a bit crazy
Some properties:
The Assoc API could be extended with any number of arguments. The resulting object represents the association of all objects passed as arguments.
Long story short, the Assoc function only cares about whether or not you pass it the right references to unseal the secret. And you should not care about passing the objects in the "right order". If I want to keep a secret from you, I'll create an unforgeable reference; I won't try to "obscure" it by not telling you the order in which you should pass the objects to Assoc
Of course, since there is no discoverability of associations, all good garbage collection optimizations are kept.
With association objects, I think we can have a uniform base to play with objects without having to invent new sorts of objects (WeakMaps, Sets, Maps, etc.) while keeping the possibility to implement (hopefully as efficiently) these abstractions.
I've had some comments from Erik Corry on Twitter. [2] "Can you add a private property to a frozen object?" => This is not discussed in the proposal [4]. I would guess not. It may
be a difference indeed between WeakMaps and private names, but I think it'd be possible to enhance the WeakMap API to allow either that a weakmap stops accepting keys or that an object is banned from all weakmaps (which are 2 different view of non-extensibility) I forgot to say that the Assoc API was a first shot and of course some additional primitives may be handy. This is what I'd like to explore.
[3] "For private names you can make a polyfill based on ES5 non-enumerability and random strings. Can u do that with Assoc?" => The polyfill would be only partial, because properties would appear
anyway with Object.getOwnPropertyNames which defeats the purpose of private names. Moreover, there would always be a risk of collision (which can't happen with private names thanks to the unforgeability property). Private names have been added because they add something that lack to the language. The Assoc API would do the same and any polyfill could only be partial (same goes for WeakMaps)
David
[1] gist.github.com/1483744 [2] twitter.com/#!/erikcorry/status/147589242246275073 [3] twitter.com/#!/erikcorry/status/147588955611729920 [4] harmony:private_name_objects