On __proto__ as a magical data property

# Jeff Walden (13 years ago)

If proto were a magical data property, what would happen if you did:

Object.defineProperty(Object.prototype, "proto", { writable: false });

and then later:

({}).proto = Object.prototype; // non-mutating "mutation"

or

({}).proto = {}; // mutating mutation

or for that matter

Object.prototype.proto = null; // non-mutating "mutation"

or

Object.prototype.proto = Object.create(null); // mutating mutation

I spent a little time experimenting implementing proto as an accessor property to see how the issues previously raised -- proxies, cross-global objects, and so on -- actually played out. It required surprisingly few special-case behaviors. SpiderMonkey's existing wrapper/proxy mechanism addresses those issues (through the "nativeCall" trap, if you're curious) without special effort.

More data's always helpful, and because Firefox is at the start of a development cycle, now is the ideal time to get it. There's no substitute for data. David Mandelin and I discussed this, and we agreed to push it to the Firefox tree to get that data. We can always back it out if necessary.

Jeff

  1. bugzilla.mozilla.org/show_bug.cgi?id=770344 Note that I didn't implement the restrictions of the wiki proposal, just a straight reimplementation. This is only to test the as-accessor proposition. If the wiki's restrictions are deemed desirable, they can be implemented atop this patch.
# Allen Wirfs-Brock (13 years ago)

Jeff,

There is a draft spec. for proto in Section B.3.1.1 of the current ES6 draft harmony:specification_drafts

There is still some controversy about exactly how this spec. should be formulated and whether the Object.prototype.proto manifests as a data property or an accessor property. But I think that spec. largely reflects general agreement on other observable issues.

In particular: changing Object.prototype.proto in any way disables the special proto semantics for any object that inherits from itObject.prototype Object.defineOwnPropertyobj, "proto", ...} does not modify [[Prototype]]. You have to use [[Put]] to do that

# Oliver Hunt (13 years ago)

JSC has not had any problems with proto being implemented as a getter/setter pair on the Object.prototype.

# Brendan Eich (13 years ago)

Jeff Walden wrote:

If proto were a magical data property, what would happen if you did:

Object.defineProperty(Object.prototype, "proto", { writable: false });

and then later:

({}).proto = Object.prototype; // non-mutating "mutation"

or

({}).proto = {}; // mutating mutation

or for that matter

Object.prototype.proto = null; // non-mutating "mutation"

or

Object.prototype.proto = Object.create(null); // mutating mutation

I don't know what you mean by "mutating mutation". In no case is the non-writable magic property's internal setter called. Right?

I spent a little time experimenting implementing proto as an accessor property to see how the issues previously raised -- proxies, cross-global objects, and so on -- actually played out. It required surprisingly few special-case behaviors. SpiderMonkey's existing wrapper/proxy mechanism addresses those issues (through the "nativeCall" trap, if you're curious) without special effort.

That's nice but it doesn't address the argument that ECMA-262, not a certain implementation, should not expose a usable setter at all (never mind wrapped and monitored).

You'd be on stronger ground if you poisoned the reflection API so the setter could not be extracted.

More data's always helpful, and because Firefox is at the start of a development cycle, now is the ideal time to get it. There's no substitute for data.

Data sounds impressive but if you're seeking proof of compatibility, you can only get confirmation that the change is not compatible.

Proving the change is compatible isn't really possible with "data", but data is not the first issue that led TC39 to agree to spec proto as a magic data property.

What's driving this desire for an accessor? Aesthetics are not a good motivation. One fewer magic data property in the language still leaves magic data properties in the language, notably Array length.

# Jeff Walden (13 years ago)

On 07/17/2012 11:37 AM, Brendan Eich wrote:

I don't know what you mean by "mutating mutation". In no case is the non-writable magic property's internal setter called. Right?

According to the draft semantics, it seems no. Somehow I missed that semantics had made their way into any draft, and I thought the wiki (rather, discussion in meeting minutes from April sometime) was the closest thing to a spec there was.

That's nice but it doesn't address the argument that ECMA-262, not a certain implementation, should not expose a usable setter at all (never mind wrapped and monitored).

You'd be on stronger ground if you poisoned the reflection API so the setter could not be extracted.

I can buy the argument the setter shouldn't be exposed, more or less. I don't think it presents intrinsic danger except in an ocap-y sense, but maybe I'm missing some concrete example. If other engines can get on board with it -- and right now JSC and Opera's implementations are not, from what I understand -- I'm happy to see that tweak in SpiderMonkey, and in the spec.

One fewer magic data property in the language still leaves magic data properties in the language, notably Array length.

One fewer magic data property is also not having to alter the fundamental [[Get]], [[Put]], and [[DefineOwnProperty]] algorithms. I think changing those algorithms presents more risk than will having a function that will change an object's prototype if it passes the right tests. (And certainly more risk than if the reflection API were poisoned and the function weren't even observable.)

It's true there's an element of aesthetics here, but I think this is a matter of substance as well. I suspect we'll have to agree to disagree on that point. (Particularly as, somewhat regrettably timing-wise, I'll be on vacation for a bit over a month starting tomorrow.)

# David Bruant (13 years ago)

Le 18/07/2012 06:32, Jeff Walden a écrit :

On 07/17/2012 11:37 AM, Brendan Eich wrote:

That's nice but it doesn't address the argument that ECMA-262, not a certain implementation, should not expose a usable setter at all (never mind wrapped and monitored).

You'd be on stronger ground if you poisoned the reflection API so the setter could not be extracted. I can buy the argument the setter shouldn't be exposed, more or less. I don't think it presents intrinsic danger except in an ocap-y sense, but maybe I'm missing some concrete example.

If the setter had the same restriction than the data property, there is no more danger (besides being able to use the setter after a "delete Object.prototype.proto", but that's not an additional danger).

Im my humble opinion, making Object.prototype.proto configurable (and actually deletable) [1] should be a much highier priority than choosing from data to accessor property. As long as Object.prototype.proto is not deletable, a data or accessor property does not matter from a security perspective, it's equally terrible. From the security perspective, it all boils down to whoever runs first. If the attacker runs first, it makes Object.prototype.proto non-configurable and the defender is likely screwed. If the defender runs first, it can "delete Object.prototype.proto" and the attacker cannot use proto in a nocive manner any longer. If Object.prototype.proto is effectively deletable, data or accessor, the difference is that if you run first and proto is an accessor, you can decide to extract the setter and use it for your own good if you have legitimate use cases. Never sharing this capability will keep you safe. Still, previous discussion led to the conclusion that "class extension" (with the new class syntax) is likely to allow the legitimate use cases without the need for a proto setter, making the choice of data or accessor an aesthetics choice. If you still disagree on that point, please provide examples.

David

[1] bugzilla.mozilla.org/show_bug.cgi?id=699945

# Andreas Rossberg (13 years ago)

On 18 July 2012 09:59, David Bruant <bruant.d at gmail.com> wrote:

Im my humble opinion, making Object.prototype.proto configurable (and actually deletable) [1] should be a much highier priority than choosing from data to accessor property. As long as Object.prototype.proto is not deletable, a data or accessor property does not matter from a security perspective, it's equally terrible. From the security perspective, it all boils down to whoever runs first. If the attacker runs first, it makes Object.prototype.proto non-configurable and the defender is likely screwed. If the defender runs first, it can "delete Object.prototype.proto" and the attacker cannot use proto in a nocive manner any longer. If Object.prototype.proto is effectively deletable, data or accessor, the difference is that if you run first and proto is an accessor, you can decide to extract the setter and use it for your own good if you have legitimate use cases. Never sharing this capability will keep you safe.

I agree with that. I'm not quite convinced that there are relevant security implication with an accessor semantics either.

I also don't think that array length counts as proper precedence. Array length is a magic property on the instance, whose magic is limited to that specific instance, whereas proto would be a magic property on the prototype, thereby introducing cross-object magic. I'd argue that's quite a different quality. Do we have any precedence for that?

It simply seems entirely incoherent to me that a single data property should exhibit different values when accessed through different receivers.

From my perspective, that's a significantly more fundamental violation of

the JS object model than the magic that comes with array length.

# David Bruant (13 years ago)

Le 18/07/2012 11:35, Andreas Rossberg a écrit :

On 18 July 2012 09:59, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

Im my humble opinion, making Object.prototype.__proto__
configurable (and actually deletable) [1] should be a much highier
priority than choosing from data to accessor property.
As long as Object.prototype.__proto__ is not deletable, a data or
accessor property does not matter from a security perspective,
it's equally terrible.
From the security perspective, it all boils down to whoever runs
first. If the attacker runs first, it makes
Object.prototype.__proto__ non-configurable and the defender is
likely screwed. If the defender runs first, it can "delete
Object.prototype.__proto__" and the attacker cannot use __proto__
in a nocive manner any longer.
If Object.prototype.__proto__ is effectively deletable, data or
accessor, the difference is that if you run first and __proto__ is
an accessor, you can decide to extract the setter and use it for
your own good if you have legitimate use cases. Never sharing this
capability will keep you safe.

I agree with that. I'm not quite convinced that there are relevant security implication with an accessor semantics either.

What was raised at the previous TC39 meeting (from the notes) was a potential security issue when an attacker would change the prototype of "host objects" in a way that the implementor didn't really think of, increasing the attack surface. I have no experience in implementing a web browser, so I won't contradict this point. For sure, ocap-style design would prevent any issue related to proto as a setter, but web browsers are written in C++ and it's unpractical to ask to redesign a web browser. I'm following the bug on the Firefox new DOM bindings and it seems like a huge amount of work from where I stand. I can only imagine it's equivalently hard for other web browsers.

I also don't think that array length counts as proper precedence. Array length is a magic property on the instance, whose magic is limited to that specific instance, whereas proto would be a magic property on the prototype, thereby introducing cross-object magic. I'd argue that's quite a different quality. Do we have any precedence for that?

It simply seems entirely incoherent to me that a single data property should exhibit different values when accessed through different receivers. From my perspective, that's a significantly more fundamental violation of the JS object model than the magic that comes with array length.

Proxies are an even more fundamental violation of the JS object model. With proxies, data properties will be able to behave almost arbitrarily differently than what we knew before proxies. Actually, with the getPrototype trap, proto could be implemented in JS (for individual objects). I don't remember when (likely around April/May 2011), but we had long discussions about which invariants proxies should enforce. Conclusions are at harmony:direct_proxies#invariant_enforcement In a nutshell, if you do Object.preventExtensions on an object, you can rely on things. Likewise if you seal or freeze a property (see definition in the page) you have some guaranteed invariants. All the rest of behaviors you used to rely on for data property is broken. The justification is that you need to break these invariants if you want to implement the array length example and NodeList numerical properties (both being reflected as data properties)

# Andreas Rossberg (13 years ago)

On 18 July 2012 12:35, David Bruant <bruant.d at gmail.com> wrote:

I also don't think that array length counts as proper precedence. Array length is a magic property on the instance, whose magic is limited to that specific instance, whereas proto would be a magic property on the prototype, thereby introducing cross-object magic. I'd argue that's quite a different quality. Do we have any precedence for that?

It simply seems entirely incoherent to me that a single data property should exhibit different values when accessed through different receivers. From my perspective, that's a significantly more fundamental violation of the JS object model than the magic that comes with array length.

Proxies are an even more fundamental violation of the JS object model.

Agreed (and one of the reservations I actually have about adding proxies). However, proxies are proxies. True, we cannot efficiently enforce many behavioural invariants for (user-defined) proxies. But that does not imply that we should feel free to actively break more invariants for (language-defined) non-proxies.

# Tom Van Cutsem (13 years ago)

2012/7/18 Andreas Rossberg <rossberg at google.com>

On 18 July 2012 12:35, David Bruant <bruant.d at gmail.com> wrote:

Proxies are an even more fundamental violation of the JS object model.

Agreed (and one of the reservations I actually have about adding proxies). However, proxies are proxies.

I disagree ;-)

I think as a byproduct of designing the Proxy API we now actually have a pretty well-defined interface to a JS object. The invariants called out in the "invariant enforcement" section on the wiki can be thought of as the minimal contract that comes with the interface. Proxies in some sense define the JS object model. Any implementation that satisfies the minimal contract does not violate the object model. Whether an implementation is a sensible implementation of the interface is a whole other matter.

True, we cannot efficiently enforce many behavioural invariants for

(user-defined) proxies. But that does not imply that we should feel free to actively break more invariants for (language-defined) non-proxies.

I absolutely agree here.

# Brendan Eich (13 years ago)

Jeff Walden wrote:

I can buy the argument the setter shouldn't be exposed, more or less. I don't think it presents intrinsicdanger except in an ocap-y sense, but maybe I'm missing some concrete example.

Nothing to do with ocap per se here. As David wrote in a followup, TC39 came to reject the accessor reflection because it degrades defense in depth. We do not only rely on one line of defense. We've had bugs where some malware can get its hands on a powerful accessor and abuse it, in spite of shallow (or just not deep enough) defenses elsewhere.

There's no "agree to disagree", we have a draft spec, we need to follow it or lobby to change it. If anyone lobbies again for accessor, they must confront the defense in depth argument against full reflecting the accessor (in particular not poisoning the setter).

# Brendan Eich (13 years ago)

David Bruant wrote:

Im my humble opinion, making Object.prototype.proto configurable (and actually deletable) [1] should be a much highier priority than choosing from data to accessor property.

Please don't thread-jack, and for no benefit (the current ES6 draft, although it puts proto in Annex B still [to be moved to the main spec], does specify it as configurable).

Fixing that SpiderMonkey bug you cite is important. I'll leave that to Jeff et al.

# Jason Orendorff (13 years ago)

On Wed, Jul 18, 2012 at 4:35 AM, Andreas Rossberg <rossberg at google.com> wrote:

I agree with that. I'm not quite convinced that there are relevant security implication with an accessor semantics either.

If we want to be sure we're not adding any power here, the proto setter should simply check that the receiver actually has the original proto property; if not, it should throw.

That would be weird, but safe, and the weirdness would be isolated in a section of the specification under the heading "proto setter" in an annex. Containment!

I also don't think that array length counts as proper precedence.

Speaking as an implementor, I see array length as a precedent to be avoided. SpiderMonkey still doesn't implement all its quirks.

I'm not surprised Waldo's experiment with accessor proto went smoothly. That's because proto as an accessor property almost makes sense.

Specifying an accessor property--plus a single special case for ObjectLiterals, which we can handle in the front end--will make less work and a nicer spec (that is, looser coupling, better isolation of special cases).

# Oliver Hunt (13 years ago)

JSC migrating proto to accessors did not add any power, if anything it removed/reduced power. The old proto was an extraordinarily magical property that existed on every object. It was not possible to remove/block it at all. So anyone could make an accessor or function or implicit conversion that could arbitrarily change the prototype of an object.

# Jason Orendorff (13 years ago)

On Tue, Jul 17, 2012 at 12:14 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

But I think that spec. largely reflects general agreement on other observable issues.

In particular: changing Object.prototype.proto in any way disables the special proto semantics for any object that inherits from itObject.prototype Object.defineOwnPropertyobj, "proto", ...} does not modify [[Prototype]]. You have to use [[Put]] to do that

Thanks for taking the time to point these out.

It's funny, both of these points only have to be agreed and specified at all if proto is to be a data property. ES5 accessor property semantics answer these questions in entirely boring, reasonable ways, without resorting to new magic:

  • changing Object.prototype.proto disables the special proto getter behavior iff you change the getter, and disables the special proto setter behavior iff you change the setter, simply because per ES5 that's how accessor properties behave.

  • Object.defineProperty(obj, "proto", {...}} doesn't modify the [[Prototype]] simply because, per ES5, it doesn't call the setter.

# Brendan Eich (13 years ago)

Ollie: IIRC, JSC used to do what Rhino still does (last I looked): specialize proto in the compiler or runtime (but either way, ahead of property lookup). True?

That's definitely not on the table. Accessor vs. magic data property is the high-order choice, which at the May TC39 meeting we made in favor of magic data property.

Jason: proto as an accessor doesn't just "almost make sense", it actually does fit in the language, cycle checking and all. That is, you could self-host it in ES5 if you self-hosted prototype chains too.

Your suggestion of the setter checking that the receiving object inherit the built-in proto before actually doing the "set" is interesting, but it seems to me that it means every set does a proxy-observable "has" operation. That's not something we do today -- maybe it's ok to add it -- but it is also overhead and opportunity for mischief. Or did you have a thought on how to silently probe for the property descriptor?

# Oliver Hunt (13 years ago)

On Jul 18, 2012, at 1:28 PM, Brendan Eich <brendan at mozilla.org> wrote:

Ollie: IIRC, JSC used to do what Rhino still does (last I looked): specialize proto in the compiler or runtime (but either way, ahead of property lookup). True?

That's definitely not on the table. Accessor vs. magic data property is the high-order choice, which at the May TC39 meeting we made in favor of magic data property.

Um. That what i just said. We used to have a magic property that could not be killed. Now we have an accessor on the prototype. That is: something less powerful that can be removed.

# Brendan Eich (13 years ago)

Brendan Eich wrote:

# Brendan Eich (13 years ago)

Oliver Hunt wrote:

On Jul 18, 2012, at 1:28 PM, Brendan Eich<brendan at mozilla.org> wrote:

Ollie: IIRC, JSC used to do what Rhino still does (last I looked): specialize proto in the compiler or runtime (but either way, ahead of property lookup). True?

That's definitely not on the table. Accessor vs. magic data property is the high-order choice, which at the May TC39 meeting we made in favor of magic data property.

Um. That what i just said.

No, we aren't using the same exact meaning of "magic".

We used to have a magic property that could not be killed. Now we have an accessor on the prototype. That is: something less powerful that can be removed.

The "could not be killed" also was not the same between JSC and SpiderMonkey.

Previous-JSC: magic data property handled before any other id lookup logic (right? that's what I'm trying to pin down), not deletable.

SpiderMonkey: magic data property that's really a "native accessor", not reflected as such, and (this part is independent and a one-line change to fix today) not configurable.

These are two different approaches. Saying what JSC had was more powerful because you couldn't override or delete it is also a different meaning of "more powerful" from the one TC39 considered in May: exposing a setter that might leak unmonitored into malware.

So we really were saying three different things using more or less the same words :-P.

# Brendan Eich (13 years ago)

Brendan Eich wrote:

Previous-JSC: magic data property handled before any other id lookup logic (right? that's what I'm trying to pin down), not deletable.

SpiderMonkey: magic data property that's really a "native accessor", not reflected as such, and (this part is independent and a one-line change to fix today) not configurable.

Sorry if the second line was unclear: SpiderMonkey's "native accessor" has no lookup priority or hardcoded id in get/set/etc. paths. It's just a property on Object.prototype.

# Jason Orendorff (13 years ago)

On Wed, Jul 18, 2012 at 3:28 PM, Brendan Eich <brendan at mozilla.org> wrote:

Your suggestion of the setter checking that the receiving object inherit the built-in proto before actually doing the "set" is interesting, but it seems to me that it means every set does a proxy-observable "has" operation. That's not something we do today -- maybe it's ok to add it -- but it is also overhead and opportunity for mischief. Or did you have a thought on how to silently probe for the property descriptor?

Yes, there's actually a nice answer for this. Consider:

// Applying __proto__ setter directly to a proxy
var proto_prop =
  Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
proto_prop.set.call(proxy, {});

// Applying a DOM getter directly to a proxy
var nodeType_prop =
  Object.getOwnPropertyDescriptor(Node.prototype, "nodeType");
nodeType_prop.get.call(proxy);

The nodeType getter is non-generic; it only works on Nodes. The proto setter should be "non-generic" too: namely, it should only work on non-Proxy objects.

As the proxies spec stands, non-generic methods applied to proxies will just throw TypeError. This is safe.

However, SpiderMonkey C++ proxies already have a mechanism that makes such calls work transparently by default: the nativeCall hook Jeff mentioned. ES proxies might or might not gain a similar mechanism. If they do, the setter call here would be transparently applied to the target by default. The hypothetical proto property check would still occur -- on the target, not the proxy.

# Jason Orendorff (13 years ago)

On Wed, Jul 18, 2012 at 3:31 PM, Brendan Eich <brendan at mozilla.org> wrote:

In particular, we don't want a proto-chain walk from [[CanPut]] and a second walk from the "has" under the proto setter for

obj.proto = safe; // not in SES code

just because we might need the "has" for

evil = Object.getOwnPropertyDescriptor(Object.prototype, 'proto').set;

// later ...

evil.call(victim, unsafe);

How would you spec this?

I would spec the desired semantics. I really don't think we should bend the language spec an iota around performance here.

But it certainly could be optimized. The extra lookup can be optimized away with a PIC, or the lookup could be fused with the proto chain cycle check. Making it possible for objects share a shape after setting proto is the low-hanging fruit here though. Last I looked, changing an object's proto was very deoptimizing in SpiderMonkey.

# Brendan Eich (13 years ago)

Jason Orendorff wrote:

On Wed, Jul 18, 2012 at 3:28 PM, Brendan Eich<brendan at mozilla.org> wrote:

Your suggestion of the setter checking that the receiving object inherit the built-in proto before actually doing the "set" is interesting, but it seems to me that it means every set does a proxy-observable "has" operation. That's not something we do today -- maybe it's ok to add it -- but it is also overhead and opportunity for mischief. Or did you have a thought on how to silently probe for the property descriptor?

Yes, there's actually a nice answer for this. Consider:

 // Applying __proto__ setter directly to a proxy
 var proto_prop =
   Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
 proto_prop.set.call(proxy, {});

 // Applying a DOM getter directly to a proxy
 var nodeType_prop =
   Object.getOwnPropertyDescriptor(Node.prototype, "nodeType");
 nodeType_prop.get.call(proxy);

The nodeType getter is non-generic; it only works on Nodes. The proto setter should be "non-generic" too: namely, it should only work on non-Proxy objects.

As the proxies spec stands, non-generic methods applied to proxies will just throw TypeError. This is safe.

However, SpiderMonkey C++ proxies already have a mechanism that makes such calls work transparently by default: the nativeCall hook Jeff mentioned. ES proxies might or might not gain a similar mechanism. If they do, the setter call here would be transparently applied to the target by default. The hypothetical proto property check would still occur -- on the target, not the proxy.

Ok, thanks -- I buy it. Good work following through on the what-ifs re: direct proxies, since we may indeed get some kind of more generic non-generic-nativeCall thing into Harmony (Allen's strawman:subclassable-builtins).

Now to get this onto the agenda for next week's meeting!

# Tom Van Cutsem (13 years ago)

2012/7/18 Jason Orendorff <jason.orendorff at gmail.com>

On Wed, Jul 18, 2012 at 3:28 PM, Brendan Eich <brendan at mozilla.org> wrote:

Your suggestion of the setter checking that the receiving object inherit the built-in proto before actually doing the "set" is interesting, but it seems to me that it means every set does a proxy-observable "has" operation. That's not something we do today -- maybe it's ok to add it -- but it is also overhead and opportunity for mischief. Or did you have a thought on how to silently probe for the property descriptor?

Yes, there's actually a nice answer for this. Consider:

// Applying __proto__ setter directly to a proxy
var proto_prop =
  Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
proto_prop.set.call(proxy, {});

// Applying a DOM getter directly to a proxy
var nodeType_prop =
  Object.getOwnPropertyDescriptor(Node.prototype, "nodeType");
nodeType_prop.get.call(proxy);

The nodeType getter is non-generic; it only works on Nodes. The proto setter should be "non-generic" too: namely, it should only work on non-Proxy objects.

As the proxies spec stands, non-generic methods applied to proxies will just throw TypeError. This is safe.

That's not how I would expect proxies to interact with Object.prototype.proto.{get,set}. Instead, if we spec mutable proto, I would expect proxies to have a {get,set}PrototypeOf trap, and the above code snippet would trigger the setPrototypeOf trap.

However, SpiderMonkey C++ proxies already have a mechanism that makes such calls work transparently by default: the nativeCall hook Jeff mentioned. ES proxies might or might not gain a similar mechanism. If they do, the setter call here would be transparently applied to the target by default. The hypothetical proto property check would still occur -- on the target, not the proxy.

Yes, this nativeCall hook is on the list of open issues for direct proxies (I tentatively named it the "applyPrimitive" trap to be more in-line with the other trap names, but that's open for debate). I intend to bring it up at next week's meeting. But again, in this specific case, I wouldn't expect that trap to be called.

Also, if we're discussing changes to the built-in [[Get]] algorithm, don't forget we have a proposed refactoring on the table that interacts better with proxies (cf. < harmony:proto_climbing_refactoring>)

In that refactoring there is no separate [[CanPut]]-walk anymore. I don't think that has much effect on this discussion though.

# David Bruant (13 years ago)

Le 19/07/2012 09:18, Tom Van Cutsem a écrit :

2012/7/18 Jason Orendorff <jason.orendorff at gmail.com <mailto:jason.orendorff at gmail.com>>

On Wed, Jul 18, 2012 at 3:28 PM, Brendan Eich <brendan at mozilla.org
<mailto:brendan at mozilla.org>> wrote:
> Your suggestion of the setter checking that the receiving object
inherit the
> built-in __proto__ before actually doing the "set" is
interesting, but it
> seems to me that it means every set does a proxy-observable
"has" operation.
> That's not something we do today -- maybe it's ok to add it --
but it is
> also overhead and opportunity for mischief. Or did you have a
thought on how
> to silently probe for the property descriptor?

Yes, there's actually a nice answer for this. Consider:

    // Applying __proto__ setter directly to a proxy
    var proto_prop =
      Object.getOwnPropertyDescriptor(Object.prototype, "__proto__");
    proto_prop.set.call(proxy, {});

    // Applying a DOM getter directly to a proxy
    var nodeType_prop =
      Object.getOwnPropertyDescriptor(Node.prototype, "nodeType");
    nodeType_prop.get.call(proxy);

The nodeType getter is non-generic; it only works on Nodes. The
__proto__ setter should be "non-generic" too: namely, it should only
work on non-Proxy objects.

As the proxies spec stands, non-generic methods applied to proxies
will
just throw TypeError.  This is safe.

That's not how I would expect proxies to interact with Object.prototype.proto.{get,set}. Instead, if we spec mutable proto, I would expect proxies to have a {get,set}PrototypeOf trap, and the above code snippet would trigger the setPrototypeOf trap.

I agree with that vision and it is making me realize that we only need a setPrototypeOf trap if the setter is extractable. If proto is a data property, the existing traps are enough. This probably weighs in favor of proto as a data property.

# Tom Van Cutsem (13 years ago)

2012/7/19 David Bruant <bruant.d at gmail.com>

I agree with that vision and it is making me realize that we only need a setPrototypeOf trap if the setter is extractable. If proto is a data property, the existing traps are enough. This probably weighs in favor of proto as a data property.

Well, it's true we don't need setPrototypeOf if we don't have a proto setter, but I don't think that counts against proto-as-accessor. Proxy authors would still need to special-case on the string "proto" in the "set" trap, for instance, if they want to mimic the expected behavior. In that sense a setPrototypeOf trap would actually be cleaner.

# Brandon Benvie (13 years ago)

Just to be clear on what's being said here, if it's possible to mutate a [[prototype]] at runtime then there needs to be a setPrototype trap or however proto gets set needs to automatically set the proxy's internal [[prototype]]. I'm, not sure if the last few messages were referring to "[[prototype]] is mutable in any form]]" or "accessor proto property" (vs. magic data proto).

Current implementations kind of have the worst of both worlds. proto is a property (which is more commonly used than Object.getPrototypeOf even for just reading value). It hits the get/set traps so it doesn't trigger any normal magical mutation of the internal [[prototype]]. Since it's a proxy you can still mimic the functionality by special casing the lookup and setting of it and keeping an internal "proto" property, but this doesn't reflect in Object.getPrototypeOf nor does it influence any internal engine functionality. Take a look at how proxy objects are labeled in the WebKit debugger if you want to see an example of how this fails.

# Andreas Rossberg (13 years ago)

On 18 July 2012 19:54, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

2012/7/18 Andreas Rossberg <rossberg at google.com>

On 18 July 2012 12:35, David Bruant <bruant.d at gmail.com> wrote:

Proxies are an even more fundamental violation of the JS object model.

Agreed (and one of the reservations I actually have about adding proxies). However, proxies are proxies.

I disagree ;-)

I think as a byproduct of designing the Proxy API we now actually have a pretty well-defined interface to a JS object. The invariants called out in the "invariant enforcement" section on the wiki can be thought of as the minimal contract that comes with the interface. Proxies in some sense define the JS object model. Any implementation that satisfies the minimal contract does not violate the object model. Whether an implementation is a sensible implementation of the interface is a whole other matter.

True, we cannot efficiently enforce many behavioural invariants for

(user-defined) proxies. But that does not imply that we should feel free to actively break more invariants for (language-defined) non-proxies.

I absolutely agree here.

Hm, I'm not quite sure I understand how you can disagree with the former but agree with the latter. ;)

# David Bruant (13 years ago)

Le 19/07/2012 11:18, Tom Van Cutsem a écrit :

2012/7/19 David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>>

I agree with that vision and it is making me realize that we only
need a setPrototypeOf trap if the setter is extractable. If
__proto__ is a data property, the existing traps are enough.
This probably weighs in favor of __proto__ as a data property.

Well, it's true we don't need setPrototypeOf if we don't have a proto setter, but I don't think that counts against proto-as-accessor. Proxy authors would still need to special-case on the string "proto" in the "set" trap, for instance, if they want to mimic the expected behavior.

I disagree. Forwarding to the target does what is expected, that is to say changing the internal [[Prototype]] of the target (if it inherits the property and proto was not deleted). Actually, I'm not sure about what the Reflect API is saying exactly about that. There will be a need to special-case if the proxy author does not want to mimic the proto setting behavior. That's my reading of the current drafts. I'm unsure it's necessarily the best situation though.

# Andreas Rossberg (13 years ago)

On 19 July 2012 00:19, Jason Orendorff <jason.orendorff at gmail.com> wrote:

On Wed, Jul 18, 2012 at 3:31 PM, Brendan Eich <brendan at mozilla.org> wrote:

In particular, we don't want a proto-chain walk from [[CanPut]] and a second walk from the "has" under the proto setter for

obj.proto = safe; // not in SES code

just because we might need the "has" for

evil = Object.getOwnPropertyDescriptor(Object.prototype, 'proto').set;

// later ...

evil.call(victim, unsafe);

How would you spec this?

I would spec the desired semantics. I really don't think we should bend the language spec an iota around performance here.

I like Jason's suggestion, and I agree that performance should be the least of all concerns for proto mutation.

(In fact, can we perhaps spec it in a way that guarantees that this operation is so expensive that it discourages everybody from still using it in the future? Like, insert a step that says "pause for 10 seconds to reconsider". :) )

# Brandon Benvie (13 years ago)

I'm pretty sure every time I solve a problem by mutating proto or using with on a proxy Andreas Rossberg and Jason Orendorff get inexplicable sharp jabs in their stomach, no matter where in the world it happens. Today I solved a problem by mutating the proto of a proxy handler and I was worried for them.

# Andreas Rossberg (13 years ago)

On 19 July 2012 12:06, Brandon Benvie <brandon at brandonbenvie.com> wrote:

I'm pretty sure every time I solve a problem by mutating proto or using with on a proxy Andreas Rossberg and Jason Orendorff get inexplicable sharp jabs in their stomach, no matter where in the world it happens. Today I solved a problem by mutating the proto of a proxy handler and I was worried for them.

That might explain the belly ache I sometimes have when waking up, like, in fact, this morning.

# Brendan Eich (13 years ago)

Andreas Rossberg wrote:

On 19 July 2012 12:06, Brandon Benvie <brandon at brandonbenvie.com <mailto:brandon at brandonbenvie.com>> wrote:

I'm pretty sure every time I solve a problem by mutating __proto__
or using `with` on a proxy Andreas Rossberg and Jason
Orendorff get inexplicable sharp jabs in their stomach, no matter
where in the world it happens. Today I solved a problem by
mutating the __proto__ of a proxy handler and I was worried for them. 

That might explain the belly ache I sometimes have when waking up, like, in fact, this morning.

The 10 second delay will only prolong your pain. :-P

proto won't go away, though.

# Jason Orendorff (13 years ago)

On Thu, Jul 19, 2012 at 5:06 AM, Brandon Benvie <brandon at brandonbenvie.com> wrote:

I'm pretty sure every time I solve a problem by mutating proto or using with on a proxy Andreas Rossberg and Jason Orendorff get inexplicable sharp jabs in their stomach, no matter where in the world it happens. Today I solved a problem by mutating the proto of a proxy handler and I was worried for them.

It's OK, I'm a masochist.

Is there any way you could make a microbenchmark out of that code?

# Geoffrey Sneddon (13 years ago)

On 18/07/12 21:28, Brendan Eich wrote:

Ollie: IIRC, JSC used to do what Rhino still does (last I looked): specialize proto in the compiler or runtime (but either way, ahead of property lookup). True?

That's definitely not on the table. Accessor vs. magic data property is the high-order choice, which at the May TC39 meeting we made in favor of magic data property.

This is the same as what Carakan had until recently (it was special-cased in both the compiler and property lookup, the latter needed to handle cases where it wasn't statically determinable).

From my point of view, what we have now is entirely less powerful than what we had previously, having an accessor property that can be deleted and having a setter that cannot be accessed ("set" on the property descriptor is poisoned). The latter is mainly done for the sake of not having a context check in [[ProtoSetter]], as the strawman:proto accessor alternative had — we simply don't have the context for most objects, and I loath the add the overhead of it. Making the setter inaccessible resolves the SES concerns without adding further overhead (excluding the one extra branch in Object.getOwnPropertyDescriptor) in code that does that does not use proto.

One of the concerns raised before was mutating prototypes of host objects: this has always been possible in Carakan and hasn't caused any issues, so I don't see any reason to remove this capability. Given we've had issues before with the overridden [[Get]], [[Put]], etc. not special-casing everywhere correctly, I'd much rather minimize special-casing and use an accessor property.