-
Notifications
You must be signed in to change notification settings - Fork 113
Another approach to brand-check #230
Comments
With that desugaring, the brand check can be easily worked around: class A {
#foo;
static assertA(obj) {
obj.#foo;
}
}
var notAnA = {};
A.assertA(notAnA); // throws
hack(notAnA, A, notAnA => {
// here brand checks are spoiled
A.assertA(notAnA); // doesn't throw
});
function hack(obj, Class, cb) {
var proto = obj.__proto__;
obj.__proto__ = new Class;
cb(obj); // obj has the brand symbol in his prototype now
obj.__proto__ = proto;
} |
function _classPrivateFieldGet(receiver, prop, brand) {
if (!Object.hasOwnProperty.apply(receiver, brand) || !receiver[brand]) {
throw new TypeError("attempted to get private field on non-instance");
}
return receiver[prop];
} It'll solve your case. |
Did you mean this? If so, I agree that it would solve the proxy issue but I don't know exactly what branding guarantees the committee want. function _classPrivateFieldGet(receiver, prop, brand) {
if (!Object.hasOwnProperty.apply(receiver, brand)) {
throw new TypeError("attempted to get private field on non-instance");
}
return receiver[prop];
} Btw, from a spec point pow this can be implemented without private symbols. The spec for
|
What about going back to WeakMap for |
What about mixins? Seems the brand needs to be hoisted to module scope, so that we can do the following (without needing a language-level let count = 0
const AMixin = (Base = Object) => {
return class A extends Base {
#priv = ++count
getOther(other) {
retur other.#priv
}
}
}
const Foo = AMixin(class {})
const Bar = AMixin(class {})
const foo = new Foo
const bar = new Foo
// intuitively:
console.assert(foo.getOther(bar) === 2) Otherwise each application of the mixin will generate a class with a different brand, although obviously in the example it is clear that the user needs the branding across instances of the same definition in source of the class, right? Am I understanding branding correctly, or is my mixin question really about something else? |
@nicolo-ribaudo Thanks. Where's the best resource to learn what exactly "branding" means? |
I think the "branding" discussion is a bit of a distraction, and the WeakMap semantics are independently motivated. See these slides for an explanation. We've discussed "branding" alternatives in this repository as well as in TC39 at length. Closing this issue, as I don't think there's much new to cover here. |
@littledan Thank for that 👍. Not sure where to ask this, but the slides say that class H {
#x;
constructor(x) {
this.#x = x;
}
get x() { return this.#x; }
}
const obj = new H();
const proxy = new Proxy(obj, {});
proxy.x is a TypeError. Doesn't that violate hard privacy? Shouldn't Why did we decide semantics should closely follow internal slot behavior? IMO it is a better goal to make a language feature that community likes, and not necessarily emulate some feature most people currently don't understand (let alone care about). What I mean is, if private fields don't behave like those Date examples... so what??? I'd rather have an awesome feature, for my classes, which isn't constrained by the goal of emulating an existing other feature of some builtin class. |
@littledan I've already seen those slides - and created this issue having your arguments in mind. I'll quote design goals you mentioned there, with my comments regarding applying of this
This proposal aims to solve
The only guarantee provided by current Do you have any specific cases which isn't covered by this proposal? P.S.
P.P.S.Could you, please, re-open this issue, since IMO it worth discussion. |
@littledan @Igmat Here's a pure question: Why must a private implementation be analogous to internal slots when internal slots have undesirable features? Is the analogy only in as far as they are essentially object instance properties hidden from public code? |
What makes them undesirable? Since they’re how the builtins behave, one argument might be that things that behave differently are bucking convention. |
By that logic, using internal slots as an implementation of class-instance private data is bucking convention for class-instance private data. That's undesirable. In the best of all cases, private members are no different than public members except that they cannot be accessed by anything not a part of the class definition. Anything beyond that is undesirable. How internal slots work in ES is perfectly fine for internal slots. But for private fields, internal slots have "features" that get in the way of several use cases. |
@ljharb Do you think about internal slots while you code in JavaScript? You don't. No one does, unless in rare cases like if you're an engine author. Adding a feature just to emulate internal slots is only a limitation being imposed for no good reason other than "it works the same". Providing people with features based on how they use JavaScript and what they want rather than emulating the engine is a better path to take. Most people don't care about the engine implementation (except for knowing what triggers performance pitfalls), they just want to write code, and if you can let them do that with less limitation, then that would be better. 99.9999% (or maybe 100%) of people don't think about internal slots when they are writing JavaScript.
Very well said! |
Yes, I do, constantly; that's why i use Your claim is overly strong; I'd say anyone using |
@ljharb I'm going to try to re-word what @trusktr was trying to say. Do developers think about internal slots when coding? Usually not. Why? They don't care how the features are implemented. They only care about the observable semantics, i.e. how they can get the language to do what they want. That doesn't imply that they aren't thinking about features that use internal slots, but rather that the feature could be implemented in some other way and the developer won't care, so long as the result is the same. Using |
They don’t actually know that - both because isArray doesn’t tell them that it inherits from Array.prototype; and because none (probably one, but off the top of my head i can’t recall any) of the array methods throw such an error. isArray tells you it’s an exotic object with a magic length property (ie, it’s a brand check alone) I’m certainly sure most people don’t know what an internal slot is nor know their semantics, but that doesn’t mean they’re not thinking about the concept. |
Red herring. When I said:
I meant that any call of the Array method with the verified object as the context will work. It doesn't matter if the object inherits from Array.prototype.
There is at least one. I've run into it before, but I'd have to do a little research to remind myself which ones. So my point stand as written. That's what the developer knows about
While potentially true, it's laughably irrelevant. Its only developers like us that bother to fuss around thinking about the inner workings of the engine. Compared to the entire ES community, I'd wager that less than 1% ever bothers to think about the concept of internal slots. And although they've existed for quite some time, before these private-fields discussions, I'd wager that even less ever knew the concept existed. |
@rdking Thanks for perfectly re-wording what I was saying. @ljharb That reasoning doesn't make sense. Just because I use JavaScript doesn't mean I automatically think about internal slots. So, just because someone uses a computer means they think about transistors? That makes no sense. A private class member feature does not need to be implemented around internal slot semantics. It needs to be implemented around what the user of the language will experience. |
@trusktr again, i'm not saying people necessarily have the word "internal slot" in their head, or know what it is. But I am saying that devs have an innate sense that certain objects have an identity - something not directly observable that makes them what they are. For example, that an array isn't just something that inherits from Array.prototype, a regex isn't just something with a |
Yes, but even that doesn't apply in most cases. And with regards to a private-member feature, what you're talking has nothing to do with it. I mean, identifying an object is one thing, but what someone can do with private members is another thing. I still don't understand how "brand check" applies to the average developer's concerns, let alone if I even understand it. What I do know is that features in this spec don't work as I'd like them to when making my own classes, of which I know the identities of (and I've never had to deal with cross-realm issues, honestly, because in all those cases I've mostly used message passing in which deserialization yields correct-realm objects, etc). |
Based on my memory of when I first was learning JS, devs have knowledge (not sense) that certain object are native entities for the language and not something created in the language. They recognize that these things have capabilities and properties not exposed to the language. And that's why they're not reproducible within the language except by the provided means. The concept of "identity" is only loosely relevant to that understanding. Nowhere in that does the concept of "internal slots" have a place. This makes all of what you posted a red herring argument. The truth is that if ES once again provided the capability of uniquely identifying the function that is running at position N in the call stack, then secure, fully encapsulated, hard private object members could easily be implemented in ES code. No internal slots or per-instance WeakMaps required. The only issue to follow that would be providing an ergonomic syntax to reduce the complexity of use. |
Source position can also give the identity of anything in the engine, cross-realm or not. What's the reason we aren't using call stacks to determine ability to access private members, if the engine already has that structure in place?
The engine doesn't have to expose that in JS land, but can still use it to validate private member access. (Note, even in non-strict mode, the JS utilities for that have flaws that prevent private member access from being blocked in all necessary cases, but these problems don't exist inside the JS engine) |
@ljharb By the way, here's a poll, "When you code in JavasScript, do you think about internal slots?": https://twitter.com/trusktr/status/1114419022366269442 I should've configured the poll to last for a week, but I accidentally left it at 24 hours. |
Uh oh!
There was an error while loading. Please reload this page.
Right now this code:
desugars to this:
As we know, it makes wrapping proxies to fail (you can check it in #227 (comment)).
The reason why it fails is
brand-check
semantic that is implemented here:The idea of such
brand-check
is that it guarantees thatconstructor
was executed, which means that allprivate
s were installed toinstance
.I want to propose change to this
brand-check
, which will guarantee thatconstructor
was executed, but won't dissmisswrapping proxies
.Consider following desugarred code:
It uses
Symbol.private
(as in this proposal, which could be implemented by simple pollyfill (like here)) instead ofWeakMap
, but efectively solves problem committee has mentioned by providing proxy-safebrand-check
mechanism. Does it make any sense for you?UPD.: code samples are updated to cover the case mentioned by @nicolo-ribaudo in #230 (comment)
The text was updated successfully, but these errors were encountered: