Wednesday, May 15, 2013

The day true became false


I occasionally get to work on a module that heavily uses google closure.  I think the original architect wanted to use it since it because google provides a compiler and a pretty robust library for doing cross browser javascript.  I also like using closure when dealing with multiple people working on a large javascript project because the compiler can enforce some rules that keep certain bad code from being checked in (besides also enforcing a linter).

The particular issue I dealt with was "fun" because I wasted an entire day trying to track down a defect that at the surface appeared to be a cross browser issue.  One action would show a page in firefox, but it would show a blank page in chrome.

Initially, the strategy I took was looking at the ajax requests in developer tools and making sure that they were firing correctly at the right times.  After determining that the requests were being fired, I then tried to figure out why the callbacks for the requests were not being fired.

One thing to note was that we were able to reproduce the issue when we were using the minified code, but were not able to reproduce the issue when the code was un-minified.

So, we eventually got in a window.console.log spiral, where we would put a log in a closure library module, and compile the source, test it, and eventually say WTF, and rinse repeat...

At one point, my pair programming partner noticed that one of the variables in the xhrio.js library was not behaving correctly.

The source was this:

goog.net.XhrIo.prototype.send = function(url, opt_method, opt_content,
                                         opt_headers) {
...
    this.active_ = true;
    window.console.log('this.active_:', this.active_);
    // should log true
This code was logging true, then all of a sudden, it logged 0. We were baffled. Why was true turning into false! Even other functions were failing.
...
   window.console.log('this.isComplete', this.isComplete());
   // showed 0
   if (this.isComplete()) {
   }
I eventually decided to go straight to the minified source and find out what isComplete was doing:
var b=Zh(a),c;a:switch(b){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:c=j;break a;default:c=l
I figured that somewhere in this file was a variable l, or a variable j that represented true, or false. Sure enough, at the top of the file, I saw this:
function e(a){throw a;}var h=void 0,j=!0,k=null,l=!1;

!0 === true, and !1 === false.

 I then figured that other code minifiers were doing this strategy. I looked at jQuery minified, and sure enough, everywhere was littered with !0 and !1 in the jQuery code in many truth checks and returns. I then figured that maybe google has already addressed this issue, so I filed an issue with them.

They told me that you have to wrap your module in a private scope: https://code.google.com/p/closure-compiler/wiki/FAQ#When_using_Advanced_Optimizations,_Closure_Compiler_adds_new_var
 What you do is pass a command line argument to the compiler.jar: --wrap-output "(function(){%output%})();"

 But I did some digging and had a daily WTF moment... We already had this wrap output, but it was removed a few months ago, because we created a separate closure module. I don't know the reasoning behind why this was removed, but I do think there should be an option to choose to minify special keywords, or even just to do the simple find/replace of true false with !0/!1.

Anyway, now you know that if you don't use wrap-output, you could be in for a lot of pain. All you need is a third-party library to not use the var keyword on simple variables.  If you don't use the var keyword, the variable will change the window scoped variable, which was where the closure minified variables were living.

If this for loop only went to 0 and never iterated greater than 0:
for(i=0; i < something.length; i++) {
   for(j=0; j < somethingelse.length; j++) {
     //...
   }
}
You would have a bad day trying to find out why true is now false...

No comments:

Post a Comment