Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problem with type coercion and string concatenation in JavaScript in Greasemonkey script on Firefox

I'm creating a GreaseMonkey script to improve the user interface of the 10k tools Stack Overflow uses. I have encountered an unreproducible and frankly bizarre problem that has confounded me and the others in the JavaScript room on SO Chat. We have yet to find the cause after several lengthy debugging sessions.

The problematic script can be found here. Source - Install


The problem occurs at line 85, the line after the 'vodoo' comment:

return (t + ' (' + +(+f.offensive + +f.spam) + ')');

It might look a little weird, but the + in front of the two variables and the inner bracket is for type coercion, the inner middle + is for addition, and the other ones are for concatenation.

Nothing special, but observant reader might note that type coercion on the inner bracket is unnecessary, since both are already type coerced to numbers, and type coercing result is useless when they get concatenated into a string anyway. Not so! Removing the + breaks the script, causing f.offensive and f.spam to be concatenated instead of added together.

Adding further console.log only makes things more confusing:

console.log(f.offensive + f.spam); // 50
console.log('' + (+f.offensive + +f.spam)); // 5, but returning this yields 50 somehow
console.log('' + (+f.offensive + +f.spam) + ''); // 50

Source: https://chat.stackoverflow.com/transcript/message/203261#203261


The problem is that this is unreproducible - running scripts like

console.log('a' + (+'3' + +'1') + 'b');

in the Firebug console yields the correct result, as does

(function(){
    return 'a' + (+'3' + +'1') + 'b';
})();

Even pulling out large chunks of the code and running them in the console does not reproduce this bug:

$('.post-menu a[id^=flag-post-]').each(function(){
    var f = {offensive: '4', spam: '1'};
    
    if(f){
        $(this).text(function(i, t){
            // Vodoo - please do not remove the '+' in front of the inner bracket
            return (t + ' (' + +(+f.offensive + +f.spam) + ')');
        });
    }
});

Tim Stone in the chatroom has reproduction instruction for those who are below 10k.


This bug only appears in Firefox - Chrome does not appear to exhibit this problem, leading me to believe that this may be a problem with either Firefox's JavaScript engine, or the Greasemonkey add-on. Am I right?

I can be found in the JavaScript room if you want more detail and/or want to discuss this.

like image 494
Yi Jiang Avatar asked Dec 17 '10 09:12

Yi Jiang


1 Answers

As part of the userscript's process, a <script> tag is injected into the page with the code retrieved by calling toString() on the function you've defined. Usually this would be fine, but it appears that there's a bug in the javascript engine used by Firefox 3.6.13 that relocates the parentheses in the expression, causing it to be evaluated in a very different way when the toString()-ified function is processed.

To illustrate this problem, we can run the following code in Firebug:

function f() { var a = '', b = '1', c = '2'; return a + '(' + (+b + +c) + ')'; };
f.toString();

This gives us this output:

function f() {
    var a = "", b = "1", c = "2";
    return a + ("(" + + b + + c + ")");
}

You'll note that the return expression has been modified. The parentheses have been relocated beyond the strings that were previously outside of them, causing the variables b and c to be coerced to strings and concatenated. This gives an unexpected result, since the expected addition never takes place. Unfortunately, this behaviour is present even when using Number() or parseInt() to coerce b and c.

There are several small modifications which change this, but the clearest is simply to save the result of the addition to a variable beforehand:

$(this).text(function(i, t){
    var c = +f.offensive + +f.spam;
    return (t + ' (' + c + ')');
});

Thankfully, this problem seems to not occur in the Firefox 4 beta, so hopefully this issue has been resolved going forward. Also, Matthew Flaschen has graciously gone ahead and filed a bug report (marked duplicate of 559438) so that the developers are made aware of this issue in either case.

like image 184
Tim Stone Avatar answered Sep 20 '22 17:09

Tim Stone