@@toStringTag spoofing for null and undefined

Allen Wirfs-Brock allen at wirfs-brock.com
Wed Jan 21 19:07:23 PST 2015


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. 

> 
> 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.

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:

```js
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:

```js
// 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


More information about the es-discuss mailing list