@@toStringTag spoofing for null and undefined

# Nicholas C. Zakas (4 years ago)

According to 19.1.3.6 Object.prototype.toString() 1, it's possible to do this:

function Foo(){}

Foo.prototype[Symbol.toStringTag] = "Null";

Object.prototype.toString.call(new Foo());   // "[object Null]"

It seems like "Null" and "Undefined" should be added to the step 17(b) list of exceptions to prevent spoofing of null and undefined values using this approach.

I couldn't think of a reason why the current behavior would make sense, but if I'm off base, feel free to correct me. :)

# Domenic Denicola (4 years ago)

I think we should remove those anti-spoofing "protections". I anticipate a discussion at TC39 next week.

# Brendan Eich (4 years ago)

Can we get a leg up, rather than wait for a f2f? This thread seems fine for further discussion and simplifying proposals.

# Allen Wirfs-Brock (4 years ago)

On Jan 19, 2015, at 2:59 PM, Nicholas C. Zakas wrote:

According to 19.1.3.6 Object.prototype.toString() 1, it's possible to do this:

function Foo(){}

Foo.prototype[Symbol.toStringTag] = "Null";

Object.prototype.toString.call(new Foo());   // "[object Null]"

It seems like "Null" and "Undefined" should be added to the step 17(b) list of exceptions to prevent spoofing of null and undefined values using this approach.

I couldn't think of a reason why the current behavior would make sense, but if I'm off base, feel free to correct me. :)

Let's be clear, the legacy usage we are trying to preserve is specifically detecting whether an object is one of those than in ES5 would of had a [[Class]] value that was one of "Function", "Array", "RegExp", "Date", "Arguments", "Error", "String", "Number", "Boolean".

In ES<=5, O.p.toString just reported, an object's [[Class]] value and no object created using a JS level constructor could have those specific [[Class]] values. So O.P.toString worked as a brand check for those specific built-in object representations.

We can't know everything JS programmers might have used that brand check for, so we need to preserve that O.p.toString behavior for those specific built-ins. Part of the behavior we want to preserve is that only the implementation provided built-ins could eturn those specific values via O.p.toString (this was a requirement introduced by ES5). The spoofing protection is design to preserve that requirement.

"null" and "undefined" were never [[Class]] values. And where actually introduced into O.p.toString by ES5.1 to fix a specific problem that was discovered after ES5 was completed(see threads starting at mail.mozilla.org/pipermail/es5-discuss/2010-June/003581.html and mail.mozilla.org/pipermail/es5-discuss/2010-June/003585.html ).

I find it a stretch to believe that anyone is depending upon using O.p.toString as a non-spoofable brand check for null or undefined.

# Domenic Denicola (4 years ago)

From: Brendan Eich [mailto:brendan at mozilla.org]

Can we get a leg up, rather than wait for a f2f? This thread seems fine for further discussion and simplifying proposals.

Well, to be clear, I'd prefer we not change anything at all. It's too late to be tweaking something that's been set in the spec for a very long time now.

But it appears that we have some relitigation on this particular topic on the agenda anyway:

@@toStringTag (rationales) (Jordan Harband)

  • Missing unspoofable builtin values (Math, JSON, Object): spec bug
  • Should built-in @@toStringTag values have { configurable: false }?
  • Should Object.prototype.toString add a prefix to all non-built-in @@toStringTag values?

It seems the "protections" in the spec so far have given some the misleading impression that we want to encourage O.p.toString as an unspoofable [[Class]] test. (Not that [[Class]] even exists anymore!) But as Allen points out, that's not the idea at all. And I'm loathe to perpetuate that usage of them---or even the impression that they should be used that way. (Nominal-typing bad! Especially in ES6/ES7/etc. where proxies/value types/etc. give us the ability to perfectly emulate the characteristics of those types!)

So if we're going to have some kind of debate about @@toStringTag anyway, I plan to be representing the side that thinks we shouldn't be protecting anything, and should just dumb it down to a simple double-dispatch protocol.

# Gary Guo (4 years ago)

Agree with Allen. As long as we can prevent spoofing for string, number, etc (ES5 defined), we should leave others as is, otherwise people may argue that Map, WeakMap, etc should be unspoofable as well.

# Brendan Eich (4 years ago)

Domenic Denicola wrote:

From: Brendan Eich [mailto:brendan at mozilla.org]

Can we get a leg up, rather than wait for a f2f? This thread seems fine for further discussion and simplifying proposals.

Well, to be clear, I'd prefer we not change anything at all. It's too late to be tweaking something that's been set in the spec for a very long time now.

But it appears that we have some relitigation on this particular topic [on the agenda anyway][1]:

Toxic terms like "relitigation", heard it from from RealAlexRussell and it sucked then too, are out of line -- we are not lawyers. Also, new TC39 rep Jordan Harband of Twitter raised this at the last meeting, asked Allen about working on it, got a green light.

@@toStringTag (rationales) (Jordan Harband)

  • Missing unspoofable builtin values (Math, JSON, Object): spec bug
  • Should built-in @@toStringTag values have { configurable: false }?
  • Should Object.prototype.toString add a prefix to all non-built-in @@toStringTag values?

It seems the "protections" in the spec so far have given some the misleading impression that we want to encourage O.p.toString as an unspoofable [[Class]] test. (Not that [[Class]] even exists anymore!) But as Allen points out, that's not the idea at all. And I'm loathe to perpetuate that usage of them---or even the impression that they should be used that way. (Nominal-typing bad!

That "X-typing bad!" line is not helpful. (What is this, a sports/beer commercial?)

Even structural typing fans such as Mark Miller have noted in their research results the benefits of nominal types for certain use-cases. Sometimes you need to know your implementation. This is the exception to the rule, but it's not always and everywhere "bad!".

Especially in ES6/ES7/etc. where proxies/value types/etc. give us the ability to perfectly emulate the characteristics of those types!)

Yes, we want to complete the MOP so nominal types are equivalent to branded structural types, a la Modula 3, and per David Ungar's position articulated many times over the years (I heard David say it to Tom Van Cutsem in person at SPLASH 2011, re: Proxies not interceding fully for all types). But we aren't there yet.

Anyway, this has little to do with getting the details of toStringTag in the best shape we can for ES6. Perhaps Jordan will weigh in, but in any case, I found his links and questions from the agenda helpful -- others may too. Here they are:

ljharb/agendas/wiki/[email protected]@toStringTag-discussion

  1. ecmascript#3506

  2. Should built-in @@toStringTag values have { configurable: false }?

  3. Should Object.prototype.toString add a prefix to all non-built-in @@toStringTag values?

# Mark S. Miller (4 years ago)

On Tue, Jan 20, 2015 at 10:05 AM, Brendan Eich <brendan at mozilla.org> wrote:

Domenic Denicola wrote:

From: Brendan Eich [mailto:brendan at mozilla.org]

Can we get a leg up, rather than wait for a f2f? This thread seems fine for further discussion and simplifying proposals.

Well, to be clear, I'd prefer we not change anything at all. It's too late to be tweaking something that's been set in the spec for a very long time now.

But it appears that we have some relitigation on this particular topic [on the agenda anyway][1]:

Toxic terms like "relitigation", heard it from from RealAlexRussell and it sucked then too, are out of line -- we are not lawyers. Also, new TC39 rep Jordan Harband of Twitter raised this at the last meeting, asked Allen about working on it, got a green light.

@@toStringTag (rationales) (Jordan Harband)

  • Missing unspoofable builtin values (Math, JSON, Object): spec bug
  • Should built-in @@toStringTag values have { configurable: false }?
  • Should Object.prototype.toString add a prefix to all non-built-in @@toStringTag values?

It seems the "protections" in the spec so far have given some the misleading impression that we want to encourage O.p.toString as an unspoofable [[Class]] test. (Not that [[Class]] even exists anymore!) But as Allen points out, that's not the idea at all. And I'm loathe to perpetuate that usage of them---or even the impression that they should be used that way. (Nominal-typing bad!

That "X-typing bad!" line is not helpful. (What is this, a sports/beer commercial?)

Even structural typing fans such as Mark Miller have noted in their research results the benefits of nominal types for certain use-cases. Sometimes you need to know your implementation. This is the exception to the rule, but it's not always and everywhere "bad!".

Yes, but I would put it more positively. Nominal and Structural typing are about different things. Neither subsume the other. Nominal types are often misunderstood to be about the string-name of types or some equally non-generative notion of type, so I prefer to use the brand terminology. The classic Types are Not Sets < dl.acm.org/citation.cfm?doid=512927.512938>, IIRC, uses the term "trademarking" instead with the same meaning. If anyone has a link to the actual pdf, please post.

Especially in ES6/ES7/etc. where proxies/value types/etc. give us the

ability to perfectly emulate the characteristics of those types!)

Yes, we want to complete the MOP so nominal types are equivalent to branded structural types, a la Modula 3, and per David Ungar's position articulated many times over the years (I heard David say it to Tom Van Cutsem in person at SPLASH 2011, re: Proxies not interceding fully for all types). But we aren't there yet.

I don't understand this paragraph. Are you saying that you want a proxy to be able to intercept and emulate the brand check, while somehow preserving the integrity implied by the brand check?

# Jordan Harband (4 years ago)

"X is bad" is, quite frankly, mostly irrelevant here. When X is bad in JS, then TC39 seems to have gone with primarily one of two choices: use strict mode to remove it, or, provide a better option Y so that developers want to move away from X and start using Y. In this case, the value of type checking doesn't matter - people do it, and so it must be considered.

There are two primary usages of Object.prototype.toString in my eyes:

  • constructs like Object.prototype.toString.call(new String('foo')) === Object.prototype.toString.call('foo') (or using that to throw an explicit error "don't use object forms of primitives") which ensures that people using my code won't fall into the very common footgun (thanks, Java) of using boxed primitives. (The same issue will apply to Symbols in ES6, but at least I can do typeof Symbol.prototype.toString.call(value) === 'symbol')
  • Testing of values from other realms (namely iframes). Object.prototype.toString.call(value) === '[object Array]' is a remarkably consistently written return value of an Array.isArray polyfill, since it works no matter how somebody has monkeyed with any global Array object.

Certainly it is trivial to construct a malicious array, for example, and pass that around breaking all sorts of things. The goal, in my opinion, of Object.prototype.toString checking is not security - it's avoiding common developer hazards. In other words, I want my code to fail fast when the developer unintentionally passes me the wrong thing - something that I think we can agree happens often.

  1. configurability of @@toStringTag It appears that TC39 considers it important to not break existing JS code with spec changes. Thus, Object.prototype.toString.call(foo), for any ES5 value "foo", must always and forever return the same value that it returned in ES5 - otherwise, existing code may follow different code paths in ES5 versus ES6, which is a hazard. This leads me to the belief that @@toStringTag values on ES5 builtins should never be changeable.

Some have replied to this, "if you don't run first, all bets are off - freeze it if you want it". Fair point! If you don't run first and keep a reference to Object.prototype.toString, you're screwed anyways. However, in ES5, if I do run first, I have 100% opt-in protection against somebody breaking things I care about. Essentially all polyfilled Array.isArray code on the web could break if it is possible to redefine Array.prototype[@@toStringTag] (in any realm, not just the one I start in), which is currently possible by default in ES6.

I believe that all built-in objects, in all realms, should have a nonconfigurable @@toStringTag for this reason.

  1. Since people are doing this type checking, if I make an object that defines its @@toStringTag value to return "Array", I will break code that does this. (Hence the protections in people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) I've filed ecmascript#3506 to discuss the missing values (namely Math, JSON, and Object, and Null, and Undefined have been mentioned as well).

Currently, the spec handles this by including a whitelist of values, and specifying that any value not in this list receives a "~" prefix. (The value of the prefix itself is irrelevant, let's please not bikeshed it).

My proposal is that rather than maintain a whitelist, and have that added complexity, that ES6 specifies that any user-defined @@toStringTag value will always and unconditionally have a prefix applied.


I believe that either of these proposals by themselves will be a win - but with both together, "nominal type checking" / branding code will continue to work, and there will be no hazards or footguns by default.

For those who dislike this kind of code, I challenge you and the committee to finalize and publish a better approach for answering the question "does this behave like an array" or "does this behave like a map" that work cross-realm (besides exhaustive duck-typing and/or feature detection), rather than attempting to simply oppress what many consider to be a valid approach, and a functional and existing one.

This is my first post to the list, so please be gentle :-) I will take any feedback provided!

# Domenic Denicola (4 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Jordan Harband

It appears that TC39 considers it important to not break existing JS code with spec changes. Thus, Object.prototype.toString.call(foo), for any ES5 value "foo", must always and forever return the same value that it returned in ES5 - otherwise, existing code may follow different code paths in ES5 versus ES6, which is a hazard. This leads me to the belief that @@toStringTag values on ES5 builtins should never be changeable.

I don't think this reasoning, or in fact the reasoning that leads to the current tilde-prefixing, is sound.

First we must realize that if we were to remove the tilde-prefixing, no deployed code would ever break. Only new code (which returns e.g. "Array" from the @@toStringTag) interacting with old code (which tests for "[object Array]") would be affected.

But in these cases developers have plenty of opportunities to test the interaction. And they quite probably are introducing the "Array" return value specifically to go down a specific code path in the old code. (For example, if they have created an array-like proxy that behaves like an Array in all other ways.) The current spec forces them to instead override Object.prototype.toString itself if they want to go down that path.

So I think the tilde-prefixing is certainly not necessary, and in fact is counterproductive in all cases I can think of.

# Jordan Harband (4 years ago)

Between the two issues I proposed, I definitely think that "something pretending to be a builtin" is far less hazardous then "altering builtins to appear to be something else". If removing the prefixing entirely is what it takes to make all (not just ES5) builtin @@toStringTag values non-configurable, I'd be happy with that.

That said, new code interacting with old code is indeed the hazard, since the old code won't know anything about ES6.

But in these cases developers have plenty of opportunities to test the interaction. And they quite probably are introducing the "Array" return value specifically to go down a specific code path in the old code. (For example, if they have created an array-like proxy that behaves like an Array in all other ways.) The current spec forces them to instead override Object.prototype.toString itself if they want to go down that path.

If it's a proxy to an actual array, wouldn't @@toStringTag be proxied too? If it's not a proxy to an actual array, why is the fact that it's a proxy relevant - it seems like your question is the same if you say "array-like object" also? Just to be sure I'm understanding.

The real issue, as you've pointed out, is that there's no easy way to answer the question "does this value behave like an array" short of either ducktyping, or treating it like one, and seeing if it breaks. Let's fix that problem instead of allowing people to misuse @@toStringTag as an attempt to fake an answer to that question.

# Boris Zbarsky (4 years ago)

On 1/20/15 3:13 PM, Jordan Harband wrote:

Between the two issues I proposed, I definitely think that "something pretending to be a builtin" is far less hazardous then "altering builtins to appear to be something else". If removing the prefixing entirely is what it takes to make all (not just ES5) builtin @@toStringTag values non-configurable, I'd be happy with that.

I don't see how that solves the problem you're trying to solve.

Consider:

var arr = []; Object.defineProperty(arr, Symbol.toStringTag, { value: "Date" });

In today's spec, Object.prototype.toString.call(arr) will be "[object ~Map]".

If the ~-prefixing is removed, this will return "[object Date]", no? The fact that @@toStringTag is non-configurable on Array.prototype doesn't matter, since the above code shadows it on the instance.

# Dmitry Soshnikov (4 years ago)

A side note: while the @toStringTag topic is actively discussed and is planned for the following meeting, I'd like to double-check the topic I raised previously related to the @toStringTag and user-level classes: [1] (basically provide default @@toStringTag being the class name), and what Nicholas's use-case shows in his initial message in this thread.

[1] esdiscuss.org/topic/default-tostringtag-for-user-classes

Dmitry

# Domenic Denicola (4 years ago)

From: Jordan Harband [mailto:ljharb at gmail.com]

Between the two issues I proposed, I definitely think that "something pretending to be a builtin" is far less hazardous then "altering builtins to appear to be something else".

I don't think that's a hazard, any more than the mutability of the many other things you can already override is a hazard.

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

Yes, we want to complete the MOP so nominal types are equivalent
to branded structural types, a la Modula 3, and per David Ungar's
position articulated many times over the years (I heard David say
it to Tom Van Cutsem in person at SPLASH 2011, re: Proxies not
interceding fully for all types). But we aren't there yet.

I don't understand this paragraph. Are you saying that you want a proxy to be able to intercept and emulate the brand check, while somehow preserving the integrity implied by the brand check?

I'm saying we (TC39, or many on it who've spoken up over the years) aspire to make the JS MOP expressive enough to allow, e.g., implementing bad old ES1-3 "host objects" via proxies, and even emulating primitive or value types (value proxies, Cormac Flanagan et al.).

For some things such as the old WebIDL "caller" extended attribute, which enabled document.all(id) in lieu of document.all[id], we've instead trimmed licit (but not de-facto) host-object expressiveness to remove the bad old thing.

IIRC David Ungar's question to Tom was "why not enable proxies to mega-program every base-level operation in the language?" I took this to mean nothing like a nominal type check could evade proxying, in David's vision. Is this plausible in your view? Anyway, HTH.

# Mark S. Miller (4 years ago)

On Tue, Jan 20, 2015 at 2:19 PM, Brendan Eich <brendan at mozilla.org> wrote:

Mark S. Miller wrote:

Yes, we want to complete the MOP so nominal types are equivalent
to branded structural types, a la Modula 3, and per David Ungar's
position articulated many times over the years (I heard David say
it to Tom Van Cutsem in person at SPLASH 2011, re: Proxies not
interceding fully for all types). But we aren't there yet.

I don't understand this paragraph. Are you saying that you want a proxy to be able to intercept and emulate the brand check, while somehow preserving the integrity implied by the brand check?

I'm saying we (TC39, or many on it who've spoken up over the years) aspire to make the JS MOP expressive enough to allow, e.g., implementing bad old ES1-3 "host objects" via proxies, and even emulating primitive or value types (value proxies, Cormac Flanagan et al.).

For some things such as the old WebIDL "caller" extended attribute, which enabled document.all(id) in lieu of document.all[id], we've instead trimmed licit (but not de-facto) host-object expressiveness to remove the bad old thing.

IIRC David Ungar's question to Tom was "why not enable proxies to mega-program every base-level operation in the language?" I took this to mean nothing like a nominal type check could evade proxying, in David's vision. Is this plausible in your view?

No it is not. It destroys the whole point of branding if passing a brand check guarantees nothing.

# Brendan Eich (4 years ago)

Brendan Eich wrote:

IIRC David Ungar's question to Tom was "why not enable proxies to mega-program every base-level operation in the language?"

LOLtypo: "meta-" not "mega-", of course.

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

IIRC David Ungar's question to Tom was "why not enable proxies to
mega-program every base-level operation in the language?" I took
this to mean nothing like a nominal type check could evade
proxying, in David's vision. Is this plausible in your view?

No it is not. It destroys the whole point of branding if passing a brand check guarantees nothing.

The counter-argument I inferred from the Q&A (this was in 2011, IIRC you were there too ;-) would answer in two parts:

  1. Needless-nominal type tests should be replaced by structural-type tests.

  2. Any remaining brand or trademark test can use object identity or equivalent unforgeable capability.

(2) can't be meta-programmed to spoof identity. But it doesn't leave anything like nominal types as found in many languages lying around as an attractive nuisance (and how, in Java!).

Now plausible?

# Brendan Eich (4 years ago)

Brendan Eich wrote:

  1. Any remaining brand or trademark test can use object identity or equivalent unforgeable capability.

(2) can't be meta-programmed to spoof identity.

Reference identity, of course -- value types/proxies want value identity defined by concatenating and freezing existing primitives, or equivalent.

# Mark S. Miller (4 years ago)

[+ungar, +tvcutsem]

On Tue, Jan 20, 2015 at 2:36 PM, Brendan Eich <brendan at mozilla.org> wrote:

Mark S. Miller wrote:

IIRC David Ungar's question to Tom was "why not enable proxies to
mega-program every base-level operation in the language?" I took
this to mean nothing like a nominal type check could evade
proxying, in David's vision. Is this plausible in your view?

No it is not. It destroys the whole point of branding if passing a brand check guarantees nothing.

The counter-argument I inferred from the Q&A (this was in 2011, IIRC you were there too ;-)

I was not there, but I talked to Tom soon afterward. CC'ing Dave and Tom, who's memory of their conversation may also be informative ;)

would answer in two parts:

  1. Needless-nominal type tests should be replaced by structural-type tests.

  2. Any remaining brand or trademark test can use object identity or equivalent unforgeable capability.

(2) can't be meta-programmed to spoof identity. But it doesn't leave anything like nominal types as found in many languages lying around as an attractive nuisance (and how, in Java!).

What I think I remember hearing from Tom is that Dave's main point, and the main argument with Tom, was precisely allowing proxies to intercede on === checks, in which case you wouldn't even have that as a reliable indicator.

Now plausible?

No.

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

(2) can't be meta-programmed to spoof identity. But it doesn't
leave anything like nominal types as found in many languages lying
around as an attractive nuisance (and how, in Java!).

What I think I remember hearing from Tom is that Dave's main point, and the main argument with Tom, was precisely allowing proxies to intercede on === checks, in which case you wouldn't even have that as a reliable indicator.

Hmm, maybe -- but does Self have a reference-identity equivalence-relation operator that can't be spoofed? Might help to ask David, but to abstract from that particular SPLASH 2011 Q&A, obviously we won't be enabling such fakery in JS.

# Mark S. Miller (4 years ago)

[resending to all, in case Dave isn't subscribed to es-discuss]

# Mark S. Miller (4 years ago)

On Tue, Jan 20, 2015 at 3:13 PM, Brendan Eich <brendan at mozilla.org> wrote:

Mark S. Miller wrote:

(2) can't be meta-programmed to spoof identity. But it doesn't
leave anything like nominal types as found in many languages lying
around as an attractive nuisance (and how, in Java!).

What I think I remember hearing from Tom is that Dave's main point, and the main argument with Tom, was precisely allowing proxies to intercede on === checks, in which case you wouldn't even have that as a reliable indicator.

Hmm, maybe -- but does Self have a reference-identity equivalence-relation operator that can't be spoofed? Might help to ask David, but to abstract from that particular SPLASH 2011 Q&A, obviously we won't be enabling such fakery in JS.

I don't get it. What are you proposing to change? It seems we have agreement on the following integrity invariants:

  • The object state invariants that were first codified in ES5 and further refined in the ES6 text, and that Direct Proxies were designed to enforce.

  • typeof x === "number" and similar, for all the typeof strings defined in ES5, as reliable but coarse brands. typeof x === "function" does not mean that x is not a proxy, but only if its target is a function (or a proxy whose target...)

  • Object.prototype.toString.call(x) === "[object Date]" and similar, but only those, since some legacy ES5 code depends on the integrity of those tests. For example, compromising this would introduce security holes into some Caja code. These are less coarse than typeof, still string-based and non-extensible as a branding mechanism.

Array.isArray(x), where true does not mean that is is not a proxy, but only if its target is an array (or a proxy whose target...)

  • === itself

  • WeakMap key lookup, since this follows from preserving the integrity of ===

  • A proxy's target cannot be mutated, though a revocable proxy's target can be dropped (by revocation)

So, given that we're keeping all the above, what are you proposing to weaken?

# Allen Wirfs-Brock (4 years ago)

On Jan 20, 2015, at 11:38 AM, Jordan Harband wrote:

"X is bad" is, quite frankly, mostly irrelevant here. When X is bad in JS, then TC39 seems to have gone with primarily one of two choices: use strict mode to remove it, or, provide a better option Y so that developers want to move away from X and start using Y. In this case, the value of type checking doesn't matter - people do it, and so it must be considered.

There are two primary usages of Object.prototype.toString in my eyes:

  • constructs like Object.prototype.toString.call(new String('foo')) === Object.prototype.toString.call('foo') (or using that to throw an explicit error "don't use object forms of primitives") which ensures that people using my code won't fall into the very common footgun (thanks, Java) of using boxed primitives. (The same issue will apply to Symbols in ES6, but at least I can do typeof Symbol.prototype.toString.call(value) === 'symbol')
  • Testing of values from other realms (namely iframes). Object.prototype.toString.call(value) === '[object Array]' is a remarkably consistently written return value of an Array.isArray polyfill, since it works no matter how somebody has monkeyed with any global Array object.

Certainly it is trivial to construct a malicious array, for example, and pass that around breaking all sorts of things. The goal, in my opinion, of Object.prototype.toString checking is not security - it's avoiding common developer hazards. In other words, I want my code to fail fast when the developer unintentionally passes me the wrong thing - something that I think we can agree happens often.

  1. configurability of @@toStringTag It appears that TC39 considers it important to not break existing JS code with spec changes. Thus, Object.prototype.toString.call(foo), for any ES5 value "foo", must always and forever return the same value that it returned in ES5 - otherwise, existing code may follow different code paths in ES5 versus ES6, which is a hazard. This leads me to the belief that @@toStringTag values on ES5 builtins should never be changeable.

Some have replied to this, "if you don't run first, all bets are off - freeze it if you want it". Fair point! If you don't run first and keep a reference to Object.prototype.toString, you're screwed anyways. However, in ES5, if I do run first, I have 100% opt-in protection against somebody breaking things I care about. Essentially all polyfilled Array.isArray code on the web could break if it is possible to redefine Array.prototype[@@toStringTag] (in any realm, not just the one I start in), which is currently possible by default in ES6.

I believe that all built-in objects, in all realms, should have a nonconfigurable @@toStringTag for this reason.

  1. Since people are doing this type checking, if I make an object that defines its @@toStringTag value to return "Array", I will break code that does this. (Hence the protections in people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring) I've filed ecmascript#3506 to discuss the missing values (namely Math, JSON, and Object, and Null, and Undefined have been mentioned as well).

Currently, the spec handles this by including a whitelist of values, and specifying that any value not in this list receives a "~" prefix. (The value of the prefix itself is irrelevant, let's please not bikeshed it).

My proposal is that rather than maintain a whitelist, and have that added complexity, that ES6 specifies that any user-defined @@toStringTag value will always and unconditionally have a prefix applied.


I believe that either of these proposals by themselves will be a win - but with both together, "nominal type checking" / branding code will continue to work, and there will be no hazards or footguns by default.

For those who dislike this kind of code, I challenge you and the committee to finalize and publish a better approach for answering the question "does this behave like an array" or "does this behave like a map" that work cross-realm (besides exhaustive duck-typing and/or feature detection), rather than attempting to simply oppress what many consider to be a valid approach, and a functional and existing one.

there are two things going on with @@toStringTag and ES6 O.p.toString

  1. @@toStringTag is primarily intended as an oopen-ended extension point that allows JS class (and other abstraction) to parameterize the descriptive string produced by O.p.toString. This parameterization is desirable (as an alternative or supplement to over-riding toString) in support of (usually debugging) use cases where O.p.toString called as a reliable means to get a displayable "[ ]" description of an object.

For that purpose, we don't really care whether an object's @@toStringTag is stable or even whether it spoofs one of the legacy tag values. It's just a hook for plugging into that extension point for that purpose.

  1. [[Class]] is gone because it did not provide an extensible way to do brand checking yet it was (indirectly through O.p.toString) being used in that way. O.p.toString never provided a way to do brand checking on values defined using JS definable constructors. When and if we decide that we can no longer live without such an extensible branding mechanism we will define one, with its one mechanism and interface. We wouldn't misuse O.p.toString for that purpose.

If developers started to use @@toStringTag as a brand that they access using O.p.toString (and we start making accommodations in support of that usage) we'd be worse off WRT branding then we are with ES5.

I would sooner see @@toStringTag go completely way than see it start to be used as a brand. If that is how we really think it will go down, then I say we should screw the convenience that we were trying to offer with #1 above and completely revert to what is essential ES5 behavior. In therms of the current spec. that means anything allocated by a ES defined constructor would answer "[object Object]".

As I said in a tweet yesterday: Our dilemma: some things that made sense in the past & people are used to, don't make future sense

Using, O.p.toString is one of those things. If improving the "produce a debug string" behavior is going to reenforce a O.p.toString usage that doesn't make sense for the future (or even for ES6), then we probably shouldn't improve O.p.toString.

(But one remaining problem, if we lost @@toStringTag then there isn't any particularly good way to explain the behavior of it when applied to DOM objects.)

# Mark S. Miller (4 years ago)

On Tue, Jan 20, 2015 at 3:22 PM, David Ungar <ungar at mac.com> wrote:

Yes, Self does have an unspoofable one, but at reflective level, not base.

In JS syntax:

reflect(setA) == reflect(setB) is an unspoofable identity test. In other words, equality of mirrors is identity of reflectees.

Hi Dave, if your language also supports a reliable identity check, and if you don't think that was a mistake[1], then I don't understand your previous question:

But on topic, the question in my mind would be: “Why does your language

support === in the first place?”

JavaScript is about as far from doing greenfield design as can be imagined, so it is not an option to reconsider having === be the way we spell that identity check. We have a huge corpus of code -- the web -- that we must not break. As Crock points out, JavaScript successfully spans a greater range of expertise, from novice to expert, than any previous language has done at such scale. At the extremes

  • Pages are created by people who don't really understand the code they are modifying, nor the semantics of the language it is written in. They simply keep fiddling with it until it no longer seems to be broken, and then ship it. I used to have more mixed feelings about this until I realized that it is precisely how I use LaTeX.

  • Libraries are crafted by experts and extensively tested across browsers and versions of browsers, to be linked into and composed with code of many others, which itself likewise spans this spectrum of expertise.

The first category, by being an accidental snapshot of what worked at a moment in time, may depend on invariants that their authors were not consciously aware of and could not articulate, but vaguely perceived (or not!) as a possible regularity that happened to work.

For the second category, such library code must make use of language-wide invariants in order to successfully co-exist and intimately interact with the wide wide range of clients into which it is linked.

Nevertheless, in successive versions of the language starting with ES3.1 we considered breaking many invariants, we decided to break some, and did so successfully. I agreed to those consensuses (consensi?), and in retrospect still agree with most of those. We carefully weighed the above factors and chose carefully which invariants to break and which to keep.

Based on the history of what === meant, I'm certain that we can't ever break ===, even once we introduce a new (reflective if you wish) high integrity identity test such as Object.is.

But here's an example of an invariant that we might get away with breaking:

(typeof x === typeof y && x == y) iff x === y

Specifically, I can see allowing future value types being able to override == to implement alleged equivalence class checks less precise than equality.

[1] If you are not making those assumptions and are questioning whether a language should have any identity check, no matter how spelled, my real answer is www.erights.org/elib/equality/grant-matcher, www.erights.org/elib/equality/grant-matcher/history.html

I don't think it is possible to write the escrow exchange agent of research.google.com/pubs/pub40673.html without using reliable identity in some form. Although James Noble and Sophia Drossopoulou have figured out how to do it without the form of distributed identity shown in that paper. This surprised me! (Anyone interested in how should let me know privately.)

# Mark S. Miller (4 years ago)

[Argh. Should have resent before reply. Better late than...]

# Mark S. Miller (4 years ago)

[resending]

# Mark S. Miller (4 years ago)

On Tue, Jan 20, 2015 at 4:44 PM, David Ungar <ungar at mac.com> wrote:

Oh, the troubles with email.

I’ll try again:

Self stratifies things into base- and meta- level. The idea is that almost all code is better off sticking to base level because that promotes encapsulation and reuse. At base-level all you can do is throw messages at objects and then throw more messages at whatever bounces off of what the first messages return. So you can always substitute an object with similar behavior for another.

At meta-level, you can interrogate the object to look inside; what slots it has, etc.

This design confers reusability, eases deployment, proxying, etc. The paper Gilad and I wrote for OOPSLA 2004 (“Mirrors…”) explains this well. I gave a talk on this just last Fall at OOPSLA -- the paper received an award. (Gilad was great at explaining the benefits of mirrors.)

In keeping with this design, our (base-level) identity operator was just another message.

For a greenfield design, all this sounds reasonable. E had identity as == at base level, but we had only debated the alternatives of have it at all (E) vs not having it at all (Joule). The idea of having it, but placing it in a more obscure part of the language surface didn't occur to us.

When I asked about why JS has ===, it was a completely serious question, hope it didn’t sound facetious: When === was put into JS, what was the motivation? I really don’t know. I designed Self’s identity operator based on my perception of why ST had (and misdefined, IMO) its identity operator.

I didn't take it as facetious, but I was worried it might distract from the current debate about what to do now, given our history.

Regarding the original motivation, Brendan?

I apologize for belaboring the obvious.

No apology necessary!

From your response, it sounds as if == is non-overidable today. Is that right?

Correct.

# Jordan Harband (4 years ago)

(Allen, I'm not sure if you saw my other reply: esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-10 )

In debugging, it's been awhile since I've seen anyone use Object#toString output - that's surprising to me as a motivation, and since it's always been pretty useless, I'd be surprised if anyone expected to be able to use it for that purpose in the future.

Brand checking is done because it's deemed necessary - it is absolutely critical to me that I have a way to determine "is this likely to behave like an array" without ducktyping or feature testing. The fact that someone could make a poison array doesn't bother me - and if I want to prevent Array from being poisoned I can do that myself - I'm concerned about people passing me things and not realizing that it's not what I wanted them to pass.

Your #2 says that "[[Class]] is gone because it did not provide an extensible way to do brand checking" - without the changes I've proposed (nonconfigurable @@toStringTag on builtins + unspoofable builtin @@toStringTag values), there's simply no way to do reliable brand checking at all. Regardless of whether you believe that brand checking is a good thing, in ES5 and prior, it exists and is in fact reliable (to some degree of reliable). I think that the value of "it produces nicer debug output for developers who don't use a debugger" does not warrant the loss of value of "somewhat reliable cross-realm brand checking".

I'd certainly prefer removing @@toStringTag entirely over the current state of it in ES6. That said, I think there's lots of value in providing a language-accessible means of previously magic language behavior, and I'd hate to lose that too.

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

Hmm, maybe -- but does Self have a reference-identity
equivalence-relation operator that can't be spoofed? Might help to
ask David, but to abstract from that particular SPLASH 2011 Q&A,
obviously we won't be enabling such fakery in JS.

I don't get it. What are you proposing to change? It seems we have agreement on the following integrity invariants:

Relax, I'm not proposing yet, just discussing. The current sub-thread started with the "nominal types bad!" assertion. I found that provocative claim inspiring; it reminded me of the SPLASH talk Tom gave. So I asked whether we can ever extend JS to allow meta-programming such that any type could be proxied.

If so, we'd have no nominal types, but we all agree that some cases need a trademark or brand integrity test that can't be subverted. In the limit, JS would have branding when needed on top of structural types, with branding based on an identity test (David showed the mirror one for Self) that cannot be proxied.

  • The object state invariants that were first codified in ES5 and further refined in the ES6 text, and that Direct Proxies were designed to enforce.

Good.

  • typeof x === "number" and similar, for all the typeof strings defined in ES5, as reliable but coarse brands. typeof x === "function" does not mean that x is not a proxy, but only if its target is a function (or a proxy whose target...)

Right. I think this takes in the (typeof x == typeof y && x == y <=> x === y) two-way implication.

  • Object.prototype.toString.call(x) === "[object Date]" and similar, but only those, since some legacy ES5 code depends on the integrity of those tests. For example, compromising this would introduce security holes into some Caja code. These are less coarse than typeof, still string-based and non-extensible as a branding mechanism.

The details for this item are at issue.

Array.isArray(x), where true does not mean that is is not a proxy, but only if its target is an array (or a proxy whose target...)

And Array is a nominal type, right? More below.

  • === itself

  • WeakMap key lookup, since this follows from preserving the integrity of ===

  • A proxy's target cannot be mutated, though a revocable proxy's target can be dropped (by revocation)

So, given that we're keeping all the above, what are you proposing to weaken?

I'm not proposing to weaken anything.

The questions raised in this thread swirl around why people continue to use Object.prototype.toString.call(x) to query some larger-than-we'd-like set of nominal types. If that set can be ring-fenced, condemned as bad legacy ("bad!"), and obsoleted over deep time, then great -- but in that future, the evolved language must not enable integrity bugs based on some brand test or other that replaces O.p.toString.call.

If we could have a more definite idea of the rules then, we could do a better job finalizing toStringTag in ES6.

But this still seems too speculative right now. I agree that the bullet list you give above, sans the O.p.toString.call item, should be enough. Yet there is room for doubt. Consider: we just amended Array.isArray to return true for a proxy whose (ultimate) target is an Array instance. Array.isArray was added to ES5 because instanceof is realm-specific because (prototype) object identity-specific.

For now it still seems that nominal types exposed via built-ins still matter in ways that can't be modeled by object identity. The answer in the future is branding, which cannot use weak maps or === or other object (reference) identity tests. ISTM we need more definite consensus on branding to finish off toStringTag in ES6.

# Mark S. Miller (4 years ago)

I understood and agree with everything (or close enough) until your last:

On Tue, Jan 20, 2015 at 7:20 PM, Brendan Eich <brendan at mozilla.org> wrote:

ISTM we need more definite consensus on branding to finish off toStringTag in ES6.

What we don't have is an inter-realm extensible branding mechanism.

  • typeof, Array.isArray, the ES5-compatible subset of O.p.toString.call's behavior are inter-realm branding, but not extensible.

  • === and WeakMap provide extensible branding, but not in a way that is usable inter-realm.

  • the rest of O.p.toString.call's behavior, i.e., the toStringTag mechanism, is an inter-realm extensible labeling mechanism, but isn't a branding mechanism.

Does the last bullet unask your question, or have I misunderstood?

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

On Tue, Jan 20, 2015 at 4:44 PM, David Ungar <ungar at mac.com <mailto:ungar at mac.com>> wrote:

Oh, the troubles with email.

I’ll try again:

Self stratifies things into base- and meta- level. The idea is
that almost all code is better off sticking to base level because
that promotes encapsulation and reuse.
At base-level all you can do is throw messages at objects and then
throw more messages at whatever bounces off of what the first
messages return.
So you can always substitute an object with similar behavior for
another.

At meta-level, you can interrogate the object to look inside; what
slots it has, etc.

This design confers reusability, eases deployment, proxying, etc.
The paper Gilad and I wrote for OOPSLA 2004 (“Mirrors…”) explains
this well.
I gave a talk on this just last Fall at OOPSLA -- the paper
 received an award. (Gilad was great at explaining the benefits of
mirrors.)

In keeping with this design, our (base-level) identity operator
was just another message.

For a greenfield design, all this sounds reasonable. E had identity as == at base level, but we had only debated the alternatives of have it at all (E) vs not having it at all (Joule). The idea of having it, but placing it in a more obscure part of the language surface didn't occur to us.

JS has a sorry history here, details below, but we might evolve (with messy redundancy, as usual on the Web due to backward compatibility) toward something like Self's nice separation.

When I asked about why JS has ===, it was a completely serious
question, hope it didn’t sound facetious: When === was put into
JS, what was the motivation?
I really don’t know. I designed Self’s identity operator based on
my perception of why ST had (and misdefined, IMO) its identity
operator.

I didn't take it as facetious, but I was worried it might distract from the current debate about what to do now, given our history.

Regarding the original motivation, Brendan?

As is well known, I hacked JS in 10 days. I had only == (and != of course), along with the other C operators plus >>> from Java.

At first I believe == was an equivalence relation ignoring NaN. But I had Perl 4 and Borland "Loose-C" (Lucy, get it) and other influences pressing on me, some from Netscape early adopters of "Mocha" (JS's first code name), who were creating very early HTML scripts to manipulate numeric form fields. These folks asked for implicit string to number and other conversions.

I was an idiot: I gave them what they wanted.

When the smoke cleared by late summer 1995 and the first Netscape 2 beta that shipped "LiveScript" (second code-name), we were left with == damaged by implicit conversions, much as in other mid-90s amateur-hour dynamic languages.

When Netscape took JS for standardization to Ecma in fall 1996, I hustled to re-implement (in SpiderMonkey, which still lives and now has JITs, generation GC, the works), and I made a hard compatibility break under opt-in versioning. If you wrote

<script language="JavaScript1.2">...</script>

then the code elided via ... would get an == that is identical to the === we know today, the one codified in ECMA-262 Edition 1 (1997).

During ES1 standardization, I tried briefly to talk Shon Katzenberger of MS into making a clean break, a kind of "flag day": Netscape and MS IE would both change the meaning of == on a certain release date. This was not workable, of course: Prisoner's Dilemma browser market share game.

As a fallback, I tried (also briefly) to promote opt-in versioning, but it was a non-starter then, just as it remains now (1JS FTW!). Versioning is an anti-pattern on the Web.

Shon put us out of my misery by counter-proposing that we add === and !==, which he was spec'ing in draft ES1 and had (IIRC) prototyped. Sold! I try not to look back, I'm all "cringed out". (But == stinks still; can it be reformed? See below.)

I apologize for belaboring the obvious.

No apology necessary!

Definitely not!

From your response, it sounds as if == is non-overidable today. Is
that right?

Correct.

For value objects in ES7, I've proposed that many operators become base-level messages (dispatch details for dyadic operators not to be digressed upon here ;-), to use the nice language David used above. From slide 4 of www.slideshare.net/BrendanEich/value-objects2,

• | ^ & • == • < <= • << >> >>> • + - • * / % • unary- unary+ boolean-test(!!) ~

The other operators save === and !== (which cannot be overridden) are derived algebraically to preserve important identities.

Some sketchy later slides in that deck try for opt-in reform of == to undo the implicit conversions that make it intransitive for numbers and strings; I was just sketching. Point here is that with enough work on operators and value objects, I bet we could recover a better ==. We'd still be stuck with === but at least === is reliable without patching.

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

I understood and agree with everything (or close enough) until your last:

On Tue, Jan 20, 2015 at 7:20 PM, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

ISTM we need more definite consensus on branding to finish off
toStringTag in ES6.

What we don't have is an inter-realm extensible branding mechanism.

  • typeof, Array.isArray, the ES5-compatible subset of O.p.toString.call's behavior are inter-realm branding, but not extensible.

  • === and WeakMap provide extensible branding, but not in a way that is usable inter-realm.

  • the rest of O.p.toString.call's behavior, i.e., the toStringTag mechanism, is an inter-realm extensible labeling mechanism, but isn't a branding mechanism.

Does the last bullet unask your question, or have I misunderstood?

Not sure what my question is, referenced in your last sentence. The call for more definite branding thinking for ES7 before we nail down toStringTag for ES6? That's not a question :-P.

Anyway, toStringTag in draft ES6 is not a branding mechanism, but I agree with Jordan that this is a problem! People have been using O.p.toString.call for brand tests, whether we like it or not.

Allen's right that we don't want to make things worse by paving a cowpath toward "bad branding", but is the ES6 draft's neither-fish-nor-fowl toStringTag situation truly better? I think not.

Surgically removing toStringTag API surface is worth another look. We'd be no worse, ignoring the DOM and other specs that build on the core ECMA-262 spec, than we were with ES5.

Idea: we could let those other specs use @@toStringTag as an extension mechanism, but ES6 would not expose it to userland via Symbol.toStringTag, nor would ES6 do any "~"-prefixing. We'd rely on WHATWG and W3C spec authors to play nice and not spoof core built-in tag-names.

What to do about class MyArray extends Array {...}, where one wants "[object MyArray]" from O.p.toString.call on an instance of MyArray? ES6 class syntax could set @@toStringTag under the hood, another bit of magic for which no desugaring (in the Felleisen translation-not-compilation sense) yet exists, akin to super. Grist for the ES7 mill.

This seems the safest play to me at the moment, and it gets rid of the prefixing and configurability issues, but not the whitelist. The whitelist rather becomes a blacklist of names that cause an early error, e.g. for class Array {...}. Errors are more future-friendly, especially early errors. We can perhaps relax some later.

Would this be too restrictive, in terms of throwing on friendly mocks such as class RegExp {...} that wraps a bona fide JS regexp? Perhaps, but it still seems safer. We need to finish ES6 without overcommitting to, and over-specifying, stuff we may regret later.

# Mark S. Miller (4 years ago)

Good, I understand now. I agree with all this. In ES6 the common cases have to work right, and no guarantees compromised. Beyond the common cases, extensibility we're confident in is great now. Otherwise, place restriction where needed now (static and dynamic errors, inaccessible symbols) to make the world safe for the extensibility directions we think we are likely to want in the future.

No exposed @toStringTag in ES6. Not because this extension point could grow into an extensible branding mechanism (it can't), but because we're not yet clear on how to prevent it from being mistaken for one.

A fair summary?

# Brendan Eich (4 years ago)

Mark S. Miller wrote:

Good, I understand now. I agree with all this. In ES6 the common cases have to work right, and no guarantees compromised. Beyond the common cases, extensibility we're confident in is great now. Otherwise, place restriction where needed now (static and dynamic errors, inaccessible symbols) to make the world safe for the extensibility directions we think we are likely to want in the future.

No exposed @toStringTag in ES6. Not because this extension point could grow into an extensible branding mechanism (it can't), but because we're not yet clear on how to prevent it from being mistaken for one.

A fair summary?

Fair. Allen?

# Tom Van Cutsem (4 years ago)

2015-01-21 0:01 GMT+01:00 Mark S. Miller <erights at google.com>:

What I think I remember hearing from Tom is that Dave's main point, and the main argument with Tom, was precisely allowing proxies to intercede on === checks, in which case you wouldn't even have that as a reliable indicator.

This is also how I remember it.

# Tom Van Cutsem (4 years ago)

On Tue, Jan 20, 2015 at 4:44 PM, David Ungar <ungar at mac.com> wrote:

Self stratifies things into base- and meta- level. The idea is that almost all code is better off sticking to base level because that promotes encapsulation and reuse. At base-level all you can do is throw messages at objects and then throw more messages at whatever bounces off of what the first messages return. So you can always substitute an object with similar behavior for another.

At meta-level, you can interrogate the object to look inside; what slots it has, etc.

I subscribe to this design point-of-view (AmbientTalk followed much the same design, in Self and Smalltalk's footsteps).

I also find your observation about moving identity to the meta-level (and thus to a more "expert" part of the language) revealing. As long as the majority of regular application code uses polymorphic equality, you're good.

Ideally, we would have had '==' be an overridable operator, allowing objects to re-implement identity how they see fit. And we would have had a more obscure, but unspoofable identity test (in the form of a method call like Object.is or Object.equals). But alas, that is not JS reality.

The trouble with '===' being our reliable identity test is that it is still very convenient syntax, so developers will tend to use it even if they would have been comfortable with a polymorphic, extensible '==' test. And of course, many in our community have for a long time advocated not using '==' because it performs implicit conversions.

It's worth comparing JavaScript's situation to Java's: Java has '==' as a reliable identity test, and it has the equals() method inherited from java.lang.Object as the polymorphic test. Even in Java though, you often hear programmers complain about not being able to redefine '==' when they start to proxy certain objects. My hypothesis is that this is because '==' is again too convenient syntax, leading developers to over-use it.

Now, coming back to equals(): if only we as a community could agree to a standard duck-typed method name to perform object equality checks, we wouldn't need to change the base language at all. We don't need a static java.lang.Object.equals() method, we can just use duck-typing. The trouble with that is of course that if the language doesn't choose a standard name, the community will invent their own. And indeed, many JavaScript libraries define their own equality operators of sorts (some do double-dispatch, some do shallow structural comparisons, some do deep-equality, etc.)

So, just like Promises/A+ 'standardised' on 'then' to do promise chaining, maybe we need to rally around and promote an extensible equality test implemented as a plain common method. No fancy new syntax or language extensions required. Of course, we'd still be left with legacy code that will continue to use '==' and '==='.

From your response, it sounds as if == is non-overidable today. Is that

right?

As Brendan and Mark already pointed out, '==' is indeed non-overridable today. I'm hopeful that Brendan's value types proposal will amend that in the future. Barring community consensus on an equals() duck-typed protocol (which is unlikely to happen), I think that's our best bet in recovering the benefits of extensible equality tests while keeping nice syntax.

# Andreas Rossberg (4 years ago)

On 20 January 2015 at 20:26, Mark S. Miller <erights at google.com> wrote:

On Tue, Jan 20, 2015 at 10:05 AM, Brendan Eich <brendan at mozilla.org> wrote:

Domenic Denicola wrote:

Nominal-typing bad!

That "X-typing bad!" line is not helpful. (What is this, a sports/beer commercial?)

Even structural typing fans such as Mark Miller have noted in their research results the benefits of nominal types for certain use-cases. Sometimes you need to know your implementation. This is the exception to the rule, but it's not always and everywhere "bad!".

Yes, but I would put it more positively. Nominal and Structural typing are about different things. Neither subsume the other. Nominal types are often misunderstood to be about the string-name of types or some equally non-generative notion of type, so I prefer to use the brand terminology. The classic Types are Not Sets < dl.acm.org/citation.cfm?doid=512927.512938>, IIRC, uses the term "trademarking" instead with the same meaning. If anyone has a link to the actual pdf, please post.

Indeed. In practice, all proper type systems combine both structural and nominal elements. Both are needed. Many mainstream languages being overly structurally challenged notwithstanding.

# Andreas Rossberg (4 years ago)

On 21 January 2015 at 01:28, Mark S. Miller <erights at google.com> wrote:

  • Pages are created by people who don't really understand the code they are modifying, nor the semantics of the language it is written in. They simply keep fiddling with it until it no longer seems to be broken, and then ship it. I used to have more mixed feelings about this until I realized that it is precisely how I use LaTeX.

On a tangent, but I find this analogy questionable. LaTeX programs are normally supposed to have exactly one possible, fixed output, so you can trivially do "exhaustive testing". (Unless you are writing a package, but then you're hopefully beyond fiddling.) Not so with dynamic web pages.

# Domenic Denicola (4 years ago)

I’d urge us to be more cautious about throwing away, or postponing, the solid win that is @@toStringTag. This is something we’ve wanted for a long time.

I’ve recently gotten several requests for jsdom to support DOM-compatible return values for Object.prototype.toString 1, 2, and each time I’ve told the requestors that we’re going to have to wait for proper @@toStringTag support 3, 4 to get a solid emulation. Given that @@toStringTag looks to be on-track to ship within one or two more V8 releases, I was very excited to be able give those users what they’re asking for finally.

Even more so, on the W3C TAG we’ve recently been working on an “Extensible Web Report Card” 5, regarding how well the platform can be explained. One of our best success stories is “Explaining the DOM via JavaScript” 6, which talks about how with the advent of ES6 (proxies, weak maps, and more) we can finally self-host all of the DOM (with the exception of document.all, of course). It would be a shame to move from virtually-100% self-hostable to 0% in one decision!


As I’ve tried to emphasize, although I can understand where Jordan’s concern comes from, I don’t think it’s a compelling one. He even states:

The goal, in my opinion, of Object.prototype.toString checking is not security - it's avoiding common developer hazards.

Given this, I do not think we should be worried at all about the consequences of @@toStringTag. The scenario he envisions---where someone overrides Array.prototype[Symbol.toStringTag]---is far outside the realm of "common developer hazards." Saying that code intended for

"is this likely to behave like an array" without ducktyping or feature testing

is not resilient in the face of people modifying the built-in prototypes seems absolutely fine. As Allen has pointed out, the test is not reliable at all: even if you freeze Array.prototype[Symbol.toStringTag], I can override Array.prototype and Array.prototype.__proto__ to cause "is this likely to behave like an array" to be false no matter what Object.prototype.toString returns. (If you plan on saving away the values of those and using them on array literals only, then I'm just going to install a non-configurable throwing setter on Array.prototype[0], and you still won't be able to use them effectively. This is not a winnable game!) In Jordan's message, he states that he's not concerned about these kinds of scenarios. Instead,

I'm concerned about people passing me things and not realizing that it's not what I wanted them to pass

But in this case you should definitely not be concerned about code that has overwritten Array.prototype[Symbol.toStringTag], as it's so far outside the realm of expected API usage that it's clearly purposeful!


We've been through this "should we lock it down" dance a couple times just in my time on the committee. The most prominent analogy is in my mind Function.prototype.length, which we specifically un-locked to be user-configurable---in the same way as @@toStringTag, by making it non-writable but configurable.

And I think in general this is a good decision. For every built-in property or method, you can invent a use case of similar plausibility to "is this likely to behave like an array" that would advise toward locking down that property or method. For example, people often serialize out functions with Function.prototype.toString and parse/manipulate them---and we don't want that to be broken if someone modifies Function.prototype.toString "without realizing" the consequences, so we should freeze Function.prototype.toString! But the fallacy here is believing that we need these use cases to be resilient in the face of modified built-ins.

I understand the fact that un-locking toString-tags is a new ability in ES6 that ES5 didn't have. But we have lots of these, from Function.prototype.length onward 7. (Another good analogy is the various changes from magic own-data properties to configurable getters---now people can override those getters and break various expectations.) Nothing of value is lost, because if a collection of code messes with built-in prototypes, all of your attempted invariants (like, Object.prototype.toString.call(a) === "[object Array]" => Array.prototype.forEach will work on the object) are up in the air anyway. Much more often we're going to have people modifying them who know what they're doing, and are prepared to reap benefits from them.

# Allen Wirfs-Brock (4 years ago)

On Jan 20, 2015, at 9:42 PM, Brendan Eich wrote:

Mark S. Miller wrote:

Good, I understand now. I agree with all this. In ES6 the common cases have to work right, and no guarantees compromised. Beyond the common cases, extensibility we're confident in is great now. Otherwise, place restriction where needed now (static and dynamic errors, inaccessible symbols) to make the world safe for the extensibility directions we think we are likely to want in the future.

No exposed @toStringTag in ES6. Not because this extension point could grow into an extensible branding mechanism (it can't), but because we're not yet clear on how to prevent it from being mistaken for one.

A fair summary?

Fair. Allen?

It's a fair summary of where we were in the discussion last night. But after having a night to stew on it, I've moved on and my current position is very similar to Domenic's. I'll elaborate on that in a separate response.

There is no such thing as a symbol valued property key that is not exposed via reflection. All properties keys are accessible via Object.getOwnPropertyKeys. Here's all it takes to restore Symbol.toStringTag:

function restoreSymbolToStringTag(taggedObject) {
  //call this function with an object that is known to have a toStringTag value.  window might be a good candidate
  let symbolKeys = Object.getOwnPrpertyKeys(taggedObject);
  let desc = { }.toString.call(taggedObject);
  let tagMatch = /\[object (.*)]'.exec(desc);
  if (!tagMatch) return false;
  let observedTag = tagMatch[1];
  for (s of symbolKeys) {
      try {
           if (taggedObject[s]; === observedTag) {
               //found it!
               Synmbol.toStringTag = s;
               return true;
            }
        } catch() {e) {};
     }
     return false;  //not found
}
# Brendan Eich (4 years ago)

Allen Wirfs-Brock wrote:

There is no such thing as a symbol valued property key that is not exposed via reflection.

Sure -- the idea was to make the symbol spec-internal, even use an abstract operation rather than a symbol -- anything to provide a spec hook for WHATWG/W3C specs to build on, without overcommitting.

Calling @@toStringTag a "solid win" is premature. We can extend in many ways to allow the platform to be explained better in self-hosted JS. That does not mean a configurable non-writable property, "~"-prefixing, the whitelist.

We need to come to consensus on the ES6 details, implement, user-test, before declaring victory (solid, liquid, or gaseous). There are many putative solid wins in the multiverse. Lack of consensus strongly suggests specifying less, deferring (not "throwing away").

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 6:19 AM, Domenic Denicola wrote:

I’d urge us to be more cautious about throwing away, or postponing, the solid win that is @@toStringTag. This is something we’ve wanted for a long time.

I was wavering a bit last night [1], but upon further reflection I now totally agree with Domenic above.

O.p.toString has never has and never will be a reliable branding or nominal typing mechanism. It can, at best, be used as an informal classification tool that can return both false positives and false negatives. Whether it is usable for this purpose depends entirely on what the developer thinks it is they are testing for and how sensitive that usage is to such false positives/negatives if they actually occurs.

This fact, shouldn't stand in our way of making O.p.toString more extensible in support of its primary use case, which is generating descriptive strings for different object abstractions.

It doesn't matter that some developers may continue to use O.p.toString as a classifier. Or even if they start using the @@toStringTag values for that purpose. It either works for them or it doesn't. If they discover it is unreliable, they will stop using it. If it is reliable enough for their purposes, that's just fine, too.

None of this means that we shouldn't continue to explore a branding or nominal typing mechanisms for ES. But I'm confident that any mechanism we might agree upon won't be based upon O.p.toString so we shouldn't be worrying about how what we do with O.p.toString might impact any such future design or if those future designs might somehow invalidate what ES6 did with O.p.toString. I think I can safely predict that this is not going to happen. Those mechanism should not be conflated and if we developed a future branding design that actually had those problems it would simply be an indication that we don't yet have the right design.

In summary, I think the current O.p.toString spec. is just fine and there is no reason to make any change to it at this time.

Now, let's go process meta for a moment.

Last minute questioning of long standing and well vetted decisions leading to feature drop is a standards committee pathology that we should be trying to avoid. There are very few absolutely right or wrong design decisions in this business. Instead, almost every decision is balancing a complex set of trade-offs and differing opinions/preferences. Finding consensus is hard and it is easy to loose it. We could reopen the discussion on just about any ES6 feature and discover compromises we made, issues we didn't address, or alternative ways to redesign the feature. If we do this in what is literally the last week before we must be DONE it's probably going to result in a dropped feature (or an unacceptable schedule slip). And this could happen to any feature. All it takes is one or two individuals standing up and questioning some aspect of the design of a feature. By doing so at this time, they are implicitly saying, I think we have a fatal problem that must be fixed. And that's a big deal.

Of course, there may be such problems (the @@create issues were one, but note that issue was raised 6 months ago, not this week). If somebody identifies a truly fatal issues, we need to deal with it. But at this point in the process each of us needs to be very careful about breaking consensus on long standing design decisions. Probably a good thing to ask ourselves, before we raise such an issue is "is this a big enough problem that I think it wold be better to slip publication/Ecma approval by 6 months rather than publish what is already in the spec.

Shipping is winning; worse it better!

Allen

[1] esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 9:34 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

There is no such thing as a symbol valued property key that is not exposed via reflection.

Sure -- the idea was to make the symbol spec-internal, even use an abstract operation rather than a symbol -- anything to provide a spec hook for WHATWG/W3C specs to build on, without overcommitting.

Then it's not a symbol at all. It might as well be an internal slot on every objected named, for example, "[[Class]]".

Calling @@toStringTag a "solid win" is premature. We can extend in many ways to allow the platform to be explained better in self-hosted JS. That does not mean a configurable non-writable property, "~"-prefixing, the whitelist.

I agree, that the anti-spoofing is the most questionable part of the design and I would be fine with loosing it. But is it really questionable enough that we can't live with what is specified. What is the fatal flaw. What does it actually harm?

# Brendan Eich (4 years ago)

Allen Wirfs-Brock wrote:

On Jan 21, 2015, at 9:34 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

There is no such thing as a symbol valued property key that is not exposed via reflection.

Sure -- the idea was to make the symbol spec-internal, even use an abstract operation rather than a symbol -- anything to provide a spec hook for WHATWG/W3C specs to build on, without overcommitting.

Then it's not a symbol at all. It might as well be an internal slot on every objected named, for example, "[[Class]]".

Sure -- good point, I flinched and was slot to say "internal property" because we all don't like infernal slots. ;-)

Calling @@toStringTag a "solid win" is premature. We can extend in many ways to allow the platform to be explained better in self-hosted JS. That does not mean a configurable non-writable property, "~"-prefixing, the whitelist.

I agree, that the anti-spoofing is the most questionable part of the design and I would be fine with loosing it. But is it really questionable enough that we can't live with what is specified. What is the fatal flaw. What does it actually harm?

This is not the question to ask, since there won't be obvious fatal flaws in lots of things we still need to agree upon, yet the cumulative complexity will hide flaws. We need to reduce complexity to reduce risk of something bad (however non-fatal). Many small wounds add up. I know this too well from JS, which barely survived its early trials.

Let's lose what we can, to avoid letting loose the complexity/risk-hounds ;-). How would you cut anti-spoofing?

# Brendan Eich (4 years ago)

Brendan Eich wrote:

Sure -- good point, I flinched and was slot

"slow", lulz.

# Jordan Harband (4 years ago)

To reiterate, I see the issue as boiling down to two questions:

  1. Should builtins have their @@toStringTag value configurable? Can anyone provide a use case, or any value, to allowing this? If not, I think they should not be configurable. I'd be very interested to hear why it would aid debugging, or help Domenic's DOM concerns (which are totally valid and admirable), or help with extensibility?

  2. Should non-builtin JS values be able to pretend to be builtins via spoofing @@toStringTag? If the answer to (1) is "every builtin's @@toStringTag is not configurable" then I think I'm actually comfortable with a value explicitly pretending to be an Array, for example, and risking the consequences of doing that incorrectly. In this scenario, dropping the prefixing entirely makes sense to me.

However, if the answer to (1) is "builtins' @@toStringTag is configurable", then this question needs to be modified.

I see no need to drop @@toStringTag, and little need to keep the prefixing at all, if all builtins (not just ES5 ones) have a nonconfigurable @@toStringTag.

It also suddenly occurs to me that the ability to pretend to be a builtin will in fact be very useful to me, personally, for the es*-shims.

Is there anyone who wouldn't be happy with "all builtins' @@toStringTag is not configurable" and "drop the ~ prefixing completely"?

# Domenic Denicola (4 years ago)

I would not be happy with making the built-ins nonconfigurable. Just like Function.prototype.length, it should be unlocked.

# Mark Miller (4 years ago)

On Wed, Jan 21, 2015 at 12:51 PM, Jordan Harband <ljharb at gmail.com> wrote:

To reiterate, I see the issue as boiling down to two questions:

  1. Should builtins have their @@toStringTag value configurable? Can anyone provide a use case, or any value, to allowing this? If not, I think they should not be configurable. I'd be very interested to hear why it would aid debugging, or help Domenic's DOM concerns (which are totally valid and admirable), or help with extensibility?

  2. Should non-builtin JS values be able to pretend to be builtins via spoofing @@toStringTag? If the answer to (1) is "every builtin's @@toStringTag is not configurable" then I think I'm actually comfortable with a value explicitly pretending to be an Array, for example, and risking the consequences of doing that incorrectly. In this scenario, dropping the prefixing entirely makes sense to me.

However, if the answer to (1) is "builtins' @@toStringTag is configurable", then this question needs to be modified.

I see no need to drop @@toStringTag, and little need to keep the prefixing at all, if all builtins (not just ES5 ones) have a nonconfigurable @@toStringTag.

It also suddenly occurs to me that the ability to pretend to be a builtin will in fact be very useful to me, personally, for the es*-shims.

Is there anyone who wouldn't be happy with "all builtins' @@toStringTag is not configurable" and "drop the ~ prefixing completely"?

Just checking: Are we talking about adding it to each instance as unconfigurable?

# Mark S. Miller (4 years ago)

On Wed, Jan 21, 2015 at 12:57 PM, Mark Miller <erights at gmail.com> wrote: [...]

Is there anyone who wouldn't be happy with "all builtins' @@toStringTag is

not configurable" and "drop the ~ prefixing completely"?

Just checking: Are we talking about adding it to each instance as unconfigurable?

Sorry, incomplete question. Meant:

Just checking: Are we talking about adding it to each instance as a non-configurable non-writable data property?

# Jordan Harband (4 years ago)

Just checking: Are we talking about adding it to each instance as a

non-configurable non-writable data property?

Mark: No, not to each instance, but to Array.prototype, Function.prototype, etc. If someone wants to override it on a specific instance that's fine, and I don't think it's important to protect against that.

I would not be happy with making the built-ins nonconfigurable. Just like

Function.prototype.length, it should be unlocked.

Domenic: Changing Function#length makes sense to me - the Function#bind shim does nasty things to ensure "length" is set properly, for example, and it would be much easier if the length was editable. Do you have a use case in mind where you'd want to change a language builtins' @@toStringTag value?

We can always unlock it later, but it seems like we can't lock it down once it's unlocked, so without any valid use cases, it seems like a risky move to unlock it now.

# Mark Miller (4 years ago)

On Wed, Jan 21, 2015 at 1:05 PM, Jordan Harband <ljharb at gmail.com> wrote:

Just checking: Are we talking about adding it to each instance as a non-configurable non-writable data property?

Mark: No, not to each instance, but to Array.prototype, Function.prototype, etc. If someone wants to override it on a specific instance that's fine, and I don't think it's important to protect against that.

In that case, I find this unacceptable, as it breaks the branding invariant that some legacy ES5 code depends on.

# Rick Waldron (4 years ago)

On Wed Jan 21 2015 at 4:11:04 PM Mark Miller <erights at gmail.com> wrote:

On Wed, Jan 21, 2015 at 1:05 PM, Jordan Harband <ljharb at gmail.com> wrote:

Just checking: Are we talking about adding it to each instance as a non-configurable non-writable data property?

Mark: No, not to each instance, but to Array.prototype, Function.prototype, etc. If someone wants to override it on a specific instance that's fine, and I don't think it's important to protect against that.

In that case, I find this unacceptable, as it breaks the branding invariant that some legacy ES5 code depends on.

I just want to clarify, this is what you're referring to, correct?

Object.getOwnPropertyDescriptor(Object.prototype, Symbol.toStringTag)

undefined

Object.defineProperty(Object.prototype, Symbol.toStringTag, { value: "Whatever I want it to be" });

Object.prototype.toString.call(function() {}) "[object Whatever I want it to be]" Object.prototype.toString.call("A string") "[object Whatever I want it to be]" Object.prototype.toString.call({}) "[object Whatever I want it to be]" Object.prototype.toString.call(1) "[object Whatever I want it to be]"

This wouldn't be possible if all built-in prototypes had an explicitly defined @@toStringTag that was { [[Enumerable]]: false, [[Writable]]: false, [[Configurable]]: false } (which is what I believe Jordan is presenting)

# Mark S. Miller (4 years ago)

// old ES5 code

function f(allegedDate) { if (({}).toString.call(allegedDate) === "[object Date]") { JSON.stringify(allegedDate); // "[]" impossible in ES5 Array.isArray(allegedDate); // true impossible in ES5 Date.prototype.getYear.call(allegedDate); // error impossible in ES5 } }

// new ES6 code

const fakeDate = []; const defProp = Object.defineProperty; defProp(fakeDate, Symbol.toStringTag, { value: "[object Date]" }); f(fakeDate); // all ES5 impossible behaviors happen

This attack does not rely on mutating any primordials, and works perfectly well even in an environment in which all primordials have been frozen (or deeply tamper proofed).

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 11:03 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

...

I agree, that the anti-spoofing is the most questionable part of the design and I would be fine with loosing it. But is it really questionable enough that we can't live with what is specified. What is the fatal flaw. What does it actually harm?

This is not the question to ask, since there won't be obvious fatal flaws in lots of things we still need to agree upon, yet the cumulative complexity will hide flaws. We need to reduce complexity to reduce risk of something bad (however non-fatal). Many small wounds add up. I know this too well from JS, which barely survived its early trials.

Let's lose what we can, to avoid letting loose the complexity/risk-hounds ;-). How would you cut anti-spoofing?

By simply deleting step 17 of people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 2:09 PM, Mark S. Miller wrote:

// old ES5 code

function f(allegedDate) { if (({}).toString.call(allegedDate) === "[object Date]") { JSON.stringify(allegedDate); // "[]" impossible in ES5 Array.isArray(allegedDate); // true impossible in ES5 Date.prototype.getYear.call(allegedDate); // error impossible in ES5 } }

// new ES6 code

const fakeDate = []; const defProp = Object.defineProperty; defProp(fakeDate, Symbol.toStringTag, { value: "[object Date]" }); f(fakeDate); // all ES5 impossible behaviors happen

not quite. The last of your tests (getYear) will still fail because fakeDate is not internally branded as a date.

See people.mozilla.org/~jorendorff/es6-draft.html#sec-date.prototype.getyear step 1 and the definition of "this time value" in people.mozilla.org/~jorendorff/es6-draft.html#sec-properties-of-the-date-prototype-object

(BTW, did you intentionally pick an Annex B Date method?)

# Mark S. Miller (4 years ago)

Not intentional. I forgot that getDate was Annex B.

# Mark S. Miller (4 years ago)

Argh. Meant getYear of course.

# Mark S. Miller (4 years ago)

On Wed, Jan 21, 2015 at 6:01 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>

wrote:

On Jan 21, 2015, at 2:09 PM, Mark S. Miller wrote:

// old ES5 code

function f(allegedDate) { if (({}).toString.call(allegedDate) === "[object Date]") { JSON.stringify(allegedDate); // "[]" impossible in ES5 Array.isArray(allegedDate); // true impossible in ES5 Date.prototype.getYear.call(allegedDate); // error impossible in ES5 } }

// new ES6 code

const fakeDate = []; const defProp = Object.defineProperty; defProp(fakeDate, Symbol.toStringTag, { value: "[object Date]" }); f(fakeDate); // all ES5 impossible behaviors happen

not quite. The last of your tests (getYear) will still fail because fakeDate is not internally branded as a date.

Ignoring the unintended Annex B issue, that failure supports my point. In ES5 we could not have failed at this point, because this point is only reached if allegedDate is a genuine Date.

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 12:51 PM, Jordan Harband wrote:

To reiterate, I see the issue as boiling down to two questions:

  1. Should builtins have their @@toStringTag value configurable? Can anyone provide a use case, or any value, to allowing this? If not, I think they should not be configurable. I'd be very interested to hear why it would aid debugging, or help Domenic's DOM concerns (which are totally valid and admirable), or help with extensibility?

There's something that I think you may have missed. As currently spec'ed for ES6 legacy built-ins do not have @@toStringTag properties. Instead O.p.toString uses internal branding to tag them, just like in ES<6. If you add a @@toStringTag to those legacy built-ins the anti-spoofing logic still applies.

Classically the way TC39 as approached all issues like this is to say that an application (such as SES) that depends upon this level of integrity are responsible for freezing the appropriate properties (or entire prototype objects).

For built-in properties where uninformed modification by a naive programmer could cause unexpected consequences, ES6 defines them as writable: false/configurable: true. All of the @@toStringTag methods defined by ES6 fall into that category. The intent is that this prevents accidental over-write of those those properties via directly assignment. You have to go out of your way and use Object.defineProperty to modify them.

  1. Should non-builtin JS values be able to pretend to be builtins via spoofing @@toStringTag? If the answer to (1) is "every builtin's @@toStringTag is not configurable" then I think I'm actually comfortable with a value explicitly pretending to be an Array, for example, and risking the consequences of doing that incorrectly. In this scenario, dropping the prefixing entirely makes sense to me.

I'd still appreciate it if you could provide some more concrete example where this style of branding would make a difference a to one of your applications. For example, if Map had the same level O.p.toString branding integrity as Date. What would you do with it.

For example, if you coded:

if ({}.toString.call(obj) ==='[object Map]') {
 //what would you do here?
} else {
 // that is different from what you would do here?
}

However, if the answer to (1) is "builtins' @@toStringTag is configurable", then this question needs to be modified.

I see no need to drop @@toStringTag, and little need to keep the prefixing at all, if all builtins (not just ES5 ones) have a nonconfigurable @@toStringTag.

It also suddenly occurs to me that the ability to pretend to be a builtin will in fact be very useful to me, personally, for the es*-shims.

Is there anyone who wouldn't be happy with "all builtins' @@toStringTag is not configurable" and "drop the ~ prefixing completely"?

BTW, It is possible to do a built-in realm independent brand check most of the ES6 built-ins. Something I've long intended to take the time to do is to write how to do a reliable, non-distructive brand check for there ones where this is possible. Here is a start:

// all Objects and methods referenced below are assumed to be the actual named  intrinsic.  You will probably ned to capture references to the intrinsic instead of directly
// referencing them as shown below.

function isMap(m) { //similarly for Set, WeakMap, WeakSet
   try {
      Map.prototype.size.call(m);
      return true;
   } catch () {return false}
}

function isDataView(d) {
   try {
      DataView.prototype.buffer.call(d);
      return true;
    } catch () {return false}
}

function isPromise(p) {
   try {
      Promise.prototype.then.call(p);
      return true;
    } catch () {return false}
}

function isTypedArray(a) {  //any TypedArray instance
   try {
      Uin32Array.prototype.buffer.call(a);
      return true;
    catch () {return false}

function isUint8ArrayArray(a) {  //for exmaple
   try {
      return Uin8Array.prototype[Symbol.toStringTag].call(a)==="Uint8Array"; ? true : false;
    } catch () {return false}
}

function isDate(d) {  //for exmaple
   try {
      Date.prototype.getDay.call(d);
      return true;
    } catch () {return false}
}

//exercise, finish this list.  Identity any where this sort of technique won't work.

What does the above checks tell you. They tell you for each of the tested built-ins (well, except for the individual typed arrays) whether the built-ins defined for it will work on the tested object. Nothing more, nothing less.

# Allen Wirfs-Brock (4 years ago)

On Jan 21, 2015, at 6:09 PM, Mark S. Miller wrote:

On Wed, Jan 21, 2015 at 6:01 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 21, 2015, at 2:09 PM, Mark S. Miller wrote:

// old ES5 code

function f(allegedDate) { if (({}).toString.call(allegedDate) === "[object Date]") { JSON.stringify(allegedDate); // "[]" impossible in ES5

actually it isn't impossible. allegedDate could have a toJSON method that returns "[]

This is a pretty good example of why this sort of brand check is so problematic. It is based upon and supports the misperception that such a branded object will have all of the specified initial characteristics of the correspond built-in.

# Brendan Eich (4 years ago)

Allen Wirfs-Brock wrote:

On Jan 21, 2015, at 6:09 PM, Mark S. Miller wrote:

On Wed, Jan 21, 2015 at 6:01 PM, Allen Wirfs-Brock <allen at wirfs-brock.com <mailto:allen at wirfs-brock.com>> wrote:

On Jan 21, 2015, at 2:09 PM, Mark S. Miller wrote:
// old ES5 code

function f(allegedDate) {
  if (({}).toString.call(allegedDate) === "[object Date]") {
    JSON.stringify(allegedDate); // "[]" impossible in ES5

actually it isn't impossible. allegedDate could have a toJSON method that returns "[]

This is a pretty good example of why this sort of brand check is so problematic. It is based upon and supports the misperception that such a branded object will have all of the specified initial characteristics of the correspond built-in.

This is a great point, which makes me want to +1 your suggestion:

Allen Wirfs-Brock wrote:

On Jan 21, 2015, at 11:03 AM, Brendan Eich wrote:

Let's lose what we can, to avoid letting loose the complexity/risk-hounds ;-). How would you cut anti-spoofing?

By simply deleting step 17 of people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring, people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring

Then Jordan could spoof in his shim, and others could too. But Mark would worry some code became vulnerable that was not in ES5.

Mark, can you cite real code examples? Not doubting they exist, just looking for something not like the Date example that didn't quite show the vuln.

# Gary Guo (4 years ago)

Now I have a tendency to support the suggestion that cuts the anti-spoofing part. If coder wants to make an object and pretend it's a built-in, let it be. The anti-spoofing algorithm could not prevent this case:Object.prototype.toString = function(){ return '[object I_Can_Be_Anything]';}

# Mark S. Miller (4 years ago)

Of course it can, by tamper proofing (essentially, freezing) Object.prototype. None of these protections are relevant anyway in an environment in which the primordials are not locked down.

# Mark Miller (4 years ago)

Actually, I withdraw that last sentence. Such protections are relevant as well in some specialized circumstances in which the trusted code runs first and squirrels away the relevant primordials such as Object.prototype.toString before it can be corrupted. Extra care is needed to avoid using it later as savedToString.call(obj) since that would leave it open to poisoning of Function.prototype.call.

See conventions:safe_meta_programming

# Isiah Meadows (4 years ago)

From: "Mark S. Miller" <erights at google.com> To: Gary Guo <nbdd0121 at hotmail.com> Cc: "es-discuss at mozilla.org" <es-discuss at mozilla.org> Date: Sat, 24 Jan 2015 07:11:35 -0800 Subject: Re: @@toStringTag spoofing for null and undefined Of course it can, by tamper proofing (essentially, freezing)

Object.prototype. None of these protections are relevant anyway in an environment in which the primordials are not locked down.

Yeah, pretty much. That proverbial inch was given a long time ago. And the proverbial mile taken. And I highly doubt the spec is going to require Object.freeze(Object.prototype), since that prohibits future polyfills and prolyfills of the Object prototype. Also, you could always straight up overwrite it, but that's even harder to protect against. (And how many cases do you know of literally overwriting built-in prototypes?)

Or, to throw out an analog to Java, it is perfectly possible to call or even override a private method through reflection. JavaScript simply has more accessible reflection, more often useful since it's a more dynamic prototype-based OO language as opposed to a stricter class-based language.

On Sat, Jan 24, 2015 at 6:11 AM, Gary Guo <nbdd0121 at hotmail.com> wrote:

Now I have a tendency to support the suggestion that cuts the

anti-spoofing part. If coder wants to make an object and pretend it's a built-in, let it be. The anti-spoofing algorithm could not prevent this case:

Object.prototype.toString = function(){
  return '[object I_Can_Be_Anything]';
}

Or this:

function handler() {
  throw new Error("No prototype for you!");
}

Object.defineProperty(
  Object,
  'prototype',
  {
    get: handler,
    set: handler,
    enumerable: true
  });

Me thinks this isn't going to get "fixed".

# Mark Miller (4 years ago)

On Sat, Jan 24, 2015 at 2:42 PM, Isiah Meadows <impinball at gmail.com> wrote:

From: "Mark S. Miller" <erights at google.com> To: Gary Guo <nbdd0121 at hotmail.com> Cc: "es-discuss at mozilla.org" <es-discuss at mozilla.org> Date: Sat, 24 Jan 2015 07:11:35 -0800 Subject: Re: @@toStringTag spoofing for null and undefined Of course it can, by tamper proofing (essentially, freezing) Object.prototype. None of these protections are relevant anyway in an environment in which the primordials are not locked down.

Yeah, pretty much. That proverbial inch was given a long time ago. And the proverbial mile taken. And I highly doubt the spec is going to require Object.freeze(Object.prototype),

Of course not. The key is the spec allows it. SES makes use of that.

# Mark Miller (4 years ago)

Put better, the spec requires that Object.freeze(Object.prototype) works.

# Jordan Harband (4 years ago)

To summarize the discussion at today's TC39 meeting:

Given that the style of checks that Allen proposed ( esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-59 ) (using non-side-effecty non-generic methods that rely on internal slots, in a try/catch) is indeed reliable in ES3, and will continue to be reliable in ES6, any security-conscious code should update itself to use these kinds of checks rather than an Object.prototype.toString.call check. v8 (and any other implementations that are working on @@toStringTag) will leave Symbol.toStringTag behind a flag for a full two months, to give the relevant code time to release updates.

In addition, anybody who modifies a builtin so that, say, a Boolean reports itself as a Number, surely intends the effects of this change, and so there is no concern about them. In accordance with this, step 17b of people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.tostring will be removed - if a developer wants to make a non-builtin value masquerade as a builtin, they similarly are intending those effects.

I've updated and/or released the following npm packages to remain resilient with respect to this change in case anyone wants some specific examples of how to implement this:

In addition, I've closed and added similar comments to the spec bug I originally filed: ecmascript#3506

Thanks, everyone, for your thoughts and time!

# John-David Dalton (4 years ago)

Kind of a bummer. The isTypedArray example from esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-59 is incorrect. Is there an updated reference somewhere? The toStringTag result is handy because it allows checking against several tags at once without having to invoke multiple functions each with their own try-catch and all that perf baggage.

# Allen Wirfs-Brock (4 years ago)

On Jan 28, 2015, at 4:40 PM, John-David Dalton <john.david.dalton at gmail.com> wrote:

Kind of a bummer. The isTypedArray example from esdiscuss.org/topic/tostringtag-spoofing-for-null-and-undefined#content-59 is incorrect. Is there an updated reference somewhere? The toStringTag result is handy because it allows checking against several tags at once without having to invoke multiple functions each with their own try-catch and all that perf baggage.

How is it incorrect? Are you referring to the fact that both typed arrays and DataView objects have a [[ViewedArrayBuffer]] internal slot. If so, I think this is a specification but that I should fix.

# John-David Dalton (4 years ago)

Primary issue is in isTypedArray(a): Uin32Array.prototype.buffer.call(a);

Besides the typos, accessing .buffer throws in at least Chrome & Firefox. Then .buffer is an object so if it doesn't throw there's no .call to execute.

# Allen Wirfs-Brock (4 years ago)

On Jan 28, 2015, at 5:03 PM, John-David Dalton <john.david.dalton at gmail.com> wrote:

Primary issue is in isTypedArray(a): Uin32Array.prototype.buffer.call(a);

Besides the typos, accessing .buffer throws in at least Chrome & Firefox. Then .buffer is an object so if it doesn't throw there's no .call to execute.

the ES6 definition of %TypedArray%.prototype.buffer:

%TypedArray%.prototype.buffer is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let O be the this value.
    
  2. If Type(O) is not Object, throw a TypeError exception.
    
  3. If O does not have a [[ViewedArrayBuffer]] internal slot throw a TypeError exception.
    
  4. Let buffer be the value of O’s [[ViewedArrayBuffer]] internal slot.
    
  5. Return buffer.
    

ES6 expects buffer to be implemented as an accessor property. That means that the probe in my test should be: Object.getOwnProperty(Uint32Array.prototype.proto, ‘buffer’).get.call(a);

# John-David Dalton (4 years ago)

At the moment that throws too. Anyways it's something to hammer on a bit. Maybe Jordan can kick it around too.

Thanks,

# Gary Guo (4 years ago)

Are the checks for these internal slots continuing to exist? Or shall we just set the @@toStringTag on their prototype and drop these steps.

For example:

  1. If the this value is undefined, return "[object Undefined]".
  2. If the this value is null, return "[object Null]".
  3. If O has an [[ParameterMap]] internal slot, let builtinTag be "Arguments".
  4. Else, let builtinTag be "Object".
  5. Let tag be the result of GetV (O, @@toStringTag).
  6. ReturnIfAbrupt(tag).
  7. If tag is undefined, let tag be builtinTag.
  8. Else, If Type(tag) is not String, let tag be "???".
  9. Return the String value that is the result of concatenating the three Strings "[object ", tag, and "]".

Under the condition that Object.prototype[@@toStringTag], String.prototype[@@toStringTag], etc are properly set.

# Gary Guo (4 years ago)

No one have comments on this?

# Allen Wirfs-Brock (4 years ago)
# Gary Guo (4 years ago)

I propose addition of String.prototype[@@toStringTag] as well as abridgement of Object.prototype.toString. By the way, I do see a new problem in current Object.prototype.toString behavior: what shall it behave when applied on a proxy object? Since proxy do not have these internal slots, it will return [object Object] (assume no @@toStringTag defined on the proxy). However, an proxy of array will return [object Array] (see definition of IsArray). This leads to an inconsistency.

# Claude Pache (4 years ago)

Le 9 févr. 2015 à 16:15, Gary Guo <nbdd0121 at hotmail.com> a écrit :

I propose addition of String.prototype[@@toStringTag] as well as abridgement of Object.prototype.toString.

The issue with that approach, is that O.p.toString.call(String.prototype) will return "[object String]", but, for ES6, String.prototype is not a String object, leading to a false positive that could break legacy code. (Don't know whether it is a problem in practice.)

# Gary Guo (4 years ago)

String.prototype is a String object (with string value ""), Function.prototype is a function, etc. If you try on ES5 browsers you will get the same result. See the spec for the reason.

# Caitlin Potter (4 years ago)

String.prototype is a String object (with string value ""), Function.prototype is a function, etc. If you try on ES5 browsers you will get the same result. See the spec for the reason.

According to the most recent draft

21.1.2.3 String.prototype

The initial value of String.prototype is the intrinsic object %StringPrototype% (21.1.3).

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

21.1.3 Properties of the String Prototype Object The String prototype object is itself an ordinary object. It is not a String instance and does not have a [[StringData]] internal slot.

“It is not a String instance and does not have a [[StringData]] internal slot.” is essentially saying that it is n’either a string primitive value, or a String wrapper object. Whether it reflects implementation reality or not is sort of besides the point at this stage.

I believe making String non-exotic has been discussed, and if this has changed from ES5, it could be related to that.

# Gary Guo (4 years ago)

Sorry for not noticing the change. It seems the ES6 spec changes the behavior of *.prototype object. In ES5, *.prototype is itself a * object. However, because in ES5 Object.prototype.toString.call(String.prototype) will return [object String], my proposal does not break any legacy code, instead, if not implemented, legacy code could be broken.

# Claude Pache (4 years ago)

Changing String.prototype from String to non-String is indeed a risk by itself. But the issue I was mentioning is independent of that, namely that an unknown object would be misidentified as String and would throw on methods that work only on Strings, e.g.: if (getType(obj) == "String") { return obj.toUpperCase() }, where getType() is based on O.p.toString.

# Gary Guo (4 years ago)

({[Symbol.toStringTag]: 'String'}) could be identified as string as well, use the legacy method. ES6 guarantees that legacy code will work, but it does not ensure that legacy code could work well with new codes that intended to create a fake string.

# Claude Pache (4 years ago)

You misunderstood me: I wasn't referring to interaction between legacy and new code, but to legacy code by itself, e.g. on an unmaintained site, that used to work and would suddenly break (hint: "unknown object" was specifically String.prototype).

# Brendan Eich (4 years ago)

This is indeed a change from ES5. Has any major engine or other test-vehicle tried to see how web-compatible it is?

# Gary Guo (4 years ago)

The point I am trying to make is that they still work, because legacy code assumes {}.toString.call(String.prototype) returns [object String]. The only possible incompatibility is the change of String.prototype, from itself a String to an ordinary object.

P.S. I am trying to submit this to buzilla, but due to unknown reason I cannot access it.

# Allen Wirfs-Brock (4 years ago)

TC39 members were aware that changing to using ordinary objects as the prototypes of the legacy built-ins was a breaking change (for example, it changes the prototypes {}.toString value). What we agreed to do was to make that change but to undo it for individual legacy built-ins if we discovered that it would cause actual significant web breakage. We discovered that this was the case for Function.prototype and so it remains a function instances in the ES6 spec

What we need for the other legacy built-ins is real evidence that there is a problem, rather than speculation. Let us know if you are aware of a significant Web site or widely used framework that depends upon String.prototype being a String instance or any other similar dependency.

# Mark S. Miller (4 years ago)

Just wanted to let everyone know that Caja is no longer vulnerable to browsers implementing the new simpler @@toStringTag spec, so there is no longer any need to wait for us. Thanks!

This spec change was one of several issues dealt with by our latest release. If you're curious about these, see the Caja security advisory at < code.google.com/p/google-caja/wiki/SecurityAdvisory20150313>. Enjoy!