restrict mode, String.prototype, typeof and strict mode

54 views
Skip to first unread message

Ondřej Žára

unread,
Oct 12, 2012, 3:15:18 AM10/12/12
to jssh...@googlegroups.com
Hi Olov and all,

consider the following code snippet:

String.prototype.x = function() { return this + "x"; }

What is the result of running this code through restrict mode? It depends on typeof(this) within the method.

Chrome - strict mode - typeof(this) == "object"
Chrome - non-strict mode - typeof(this) == "object"
Firefox - strict mode - typeof(this) == "string"
Firefox - non-strict mode - typeof(this) == "object"

As long as typeof(this) != "string", restrict mode throws an exception (incorrectly, imho).

1) Is it correct not to consider boxed primitives?
2) Why are the browsers behaving differently? Is it Firefox's/Chrome's bug?


Sincerely,
Ondrej Zara


Olov Lassus

unread,
Oct 12, 2012, 3:45:55 AM10/12/12
to jssh...@googlegroups.com
Hello Ondrej,
thanks for trying out restrict mode and providing your feedback!

12 okt 2012 kl. 09:15 skrev Ondřej Žára <ondre...@gmail.com>:

> consider the following code snippet:
>
> String.prototype.x = function() { return this + "x"; }
>
> What is the result of running this code through restrict mode? It depends on typeof(this) within the method.
>
> Chrome - strict mode - typeof(this) == "object"
> Chrome - non-strict mode - typeof(this) == "object"
> Firefox - strict mode - typeof(this) == "string"
> Firefox - non-strict mode - typeof(this) == "object"

What you've noticed is the semantic change of boxed/primitive value this in ES5 strict mode. I can't reproduce your mismatch between Chrome and Firefox. Your Firefox example shows the result of running in a browser that support ES5, your Chrome example running in one that doesn't.

> As long as typeof(this) != "string", restrict mode throws an exception (incorrectly, imho).

That is per design. "+ is restricted to primitive strings and numbers (in any combination)". JS primitives and boxed values mix very badly. The remediation is very simple though - just explicitly convert the boxed value to a primitive, in your case via String(this):

String.prototype.x = function() { return String(this) + "x"; }

I consider ES5 strict mode primive-this harmful and a design mistake because the (non-throwing) semantics of ES5 strict mode is no longer a subset of non-strict mode. Check out slide 41 of <http://blog.lassus.se/files/javascript_the_subsets_we_use_webshaped.pdf> for a concrete example of why.

Cheers,
/Olov

Ondřej Žára

unread,
Oct 12, 2012, 4:44:22 AM10/12/12
to jssh...@googlegroups.com
Hi Olov,

thanks a lot for a rapid reply! Your jsconf.eu talk about restrict was awesome, btw :-)



What you've noticed is the semantic change of boxed/primitive value this in ES5 strict mode. I can't reproduce your mismatch between Chrome and Firefox. Your Firefox example shows the result of running in a browser that support ES5, your Chrome example running in one that doesn't.


I must have made a mistake in Chrome; my testpage now shows identical behavior in both browsers (http://jsfiddle.net/mKXZx/).

 
> As long as typeof(this) != "string", restrict mode throws an exception (incorrectly, imho).

That is per design. "+ is restricted to primitive strings and numbers (in any combination)". JS primitives and boxed values mix very badly. The remediation is very simple though - just explicitly convert the boxed value to a primitive, in your case via String(this):

String.prototype.x = function() { return String(this) + "x"; }


What about this.toString()? I somehow dislike any kind of usage of "String" and other primordial functions (in most cases).

The issue mentioned is the result of our implementation of String.prototype.lpad. Do you suggest actually using the above workaround to comply with restrict mode, or is it sufficient to know that our usage is not harmful?

Finally, where can I read about primitive-this? All articles I found re. strict do not mention this semantic change....


Thanks,
Ondrej


 

Olov Lassus

unread,
Oct 12, 2012, 4:56:48 AM10/12/12
to jssh...@googlegroups.com
12 okt 2012 kl. 10:44 skrev Ondřej Žára <ondre...@gmail.com>:

> thanks a lot for a rapid reply! Your jsconf.eu talk about restrict was awesome, btw :-)

My pleasure!

> String.prototype.x = function() { return String(this) + "x"; }
>
> What about this.toString()? I somehow dislike any kind of usage of "String" and other primordial functions (in most cases).

Sure, convert it to a primitive String by whatever means you like.

> The issue mentioned is the result of our implementation of String.prototype.lpad. Do you suggest actually using the above workaround to comply with restrict mode, or is it sufficient to know that our usage is not harmful?

My recommendation is to be very careful with using `this` inside of a custom String/Number/Boolean prototype function if you're using ES5 strict mode - whether you use restrict mode or not. My recommendation for your specific case is to explicitly do the primitive conversion via String or toString. That makes your code more robust and easier to reason about, IMO.

> Finally, where can I read about primitive-this? All articles I found re. strict do not mention this semantic change….

Check out <https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/Strict_mode#.22Securing.22_JavaScript>

I may get around to writing an "ES5 strict mode primive-this considered harmful" article some day,

/Olov

Ondřej Žára

unread,
Oct 12, 2012, 5:06:56 AM10/12/12
to jssh...@googlegroups.com

> The issue mentioned is the result of our implementation of String.prototype.lpad. Do you suggest actually using the above workaround to comply with restrict mode, or is it sufficient to know that our usage is not harmful?

My recommendation is to be very careful with using `this` inside of a custom String/Number/Boolean prototype function if you're using ES5 strict mode - whether you use restrict mode or not. My recommendation for your specific case is to explicitly do the primitive conversion via String or toString. That makes your code more robust and easier to reason about, IMO.


Hm, this puzzles me. The value of "this" inside a primordial prototype method sounds like something very predictable; something that does not need to be robust / typechecked / explicitely converted. These methods are not meant to be called via Stuff.prototype.method.call(somethingStrange); whoever does this, it is his/hers own shooting-into-a-leg.

Following the "make sure 'this' is correct" approach, one can also sanitize the "this" value in every other single method - but that sounds horrifying...


O.


 

Olov Lassus

unread,
Oct 12, 2012, 7:11:07 AM10/12/12
to jssh...@googlegroups.com
12 okt 2012 kl. 11:06 skrev Ondřej Žára <ondre...@gmail.com>:

> Hm, this puzzles me. The value of "this" inside a primordial prototype method sounds like something very predictable; something that does not need to be robust / typechecked / explicitely converted.

One would hope so.

If you're not using ES5 strict mode, then it's predictable. `this` is an object.

If you "use strict"; i.e. you are using ES5 strict mode then `this` is either an object (on ES5-compliant JS engines) or a primitive (on ES3-compliant JS engines), and you don't know unless you can control the end-user environment. Chances are that you are using a recent environment during development so your code always executes with `this` being a primitive, on your computer. Then it's very easy to end up with code that executes differently in the wild.

Because of ES5 strict mode primitive-this semantics you basically need to run your entire test suite twice - with strict mode enabled and disabled, because non throwing strict mode is no longer a subset of non-strict mode. You can remediate this by never taking advantage of the browser sometimes converting the object to a primitive for you, and it's both simple and cheap to do so.

/Olov

Ondřej Žára

unread,
Oct 12, 2012, 7:54:12 AM10/12/12
to jssh...@googlegroups.com

Yes, this auto-conversion is basically what I am talking about. In our case (String.prototype.lpad), this implicit conversion is the result of using an "+" operator. But if the set of admissible "this" values contains only "object" and "string", the "+" works the same as String() or .toString() conversion, right?

I still want to restrict myself to a proper language subset; for instance by "adding" only meaningful values. So, is "new String('a') +'b'" considered harmful?


O.

/Olov

Olov Lassus

unread,
Oct 12, 2012, 8:16:53 AM10/12/12
to jssh...@googlegroups.com
12 okt 2012 kl. 13:54 skrev Ondřej Žára <ondre...@gmail.com>:

> Yes, this auto-conversion is basically what I am talking about. In our case (String.prototype.lpad), this implicit conversion is the result of using an "+" operator. But if the set of admissible "this" values contains only "object" and "string", the "+" works the same as String() or .toString() conversion, right?

Sure. But if you're part of a team, are you sure that all of your team members are aware of the two different cases, after all perhaps they will maintain the code?

It's likely that one starts applying other operators too, such as ===. Now for example `this === 1` (in a Number prototype function) yields two completely different results depending on whether you run it in ES5 strict mode or not. Or perhaps you want to check whether the string is empty and do that with a check for truthyness. Unfortunately the primitive empty string is falsy while the boxed empty string is truthy. Or even worse, someone may choose to `return this` and now it's up to each and every call site to make sure that they handle the return value of the prototype function properly both when it's boxed and when it's a primitive. I'm not making this scenario up by the way - JSLint had code that did exactly this when I applied restrict mode to it, see <http://blog.lassus.se/2011/04/i-made-jslint-restrict-mode-clean-heres.html> ("third change").

> I still want to restrict myself to a proper language subset; for instance by "adding" only meaningful values. So, is "new String('a') +'b'" considered harmful?

I don't consider String + string harmful in itself, however I think that mixing Boxed and primitives values in JS generally leads to more error-prone programs that are harder to reason about, for very little added value in return. If you disagree but still want to use restrict mode then you can either put the /* @loose */ annotation on the + expression, or make sure that the String.prototype.Ipad function is created in a scope that is not surrounded by a "use restrict" directive. You can also put the /* @loose */ annotation on the entire function if you wish to.

/Olov

Reply all
Reply to author
Forward
0 new messages