Don’t Worry, It’s a Compiler Bug
System bugs do happen. But not often. Read on to hear the sequence of events that led to the only time I found a compiler bug in 19 years of programming.
Join the DZone community and get the full member experience.Join For Free
When I was a kid writing DOS games in Pascal, I would often get stuck on a problem. After I had exhausted all my knowledge and all the help files, I would conclude, without a doubt, that something was wrong with my machine.
When I was a teenager making subpar websites in PHP, I would often get stuck on a problem. After I had exhausted all my knowledge and all the help files and all the internet I could find, I would conclude again, without a doubt, that PHP was being shitty and running my code wrong.
“It’s never the compiler’s fault.”
~ Smart People
This is hyperbole, of course. System bugs do happen. But not often.
A programmer can go his or her entire career without hitting a compiler, operating system, or hardware bug. In fact, studies in the late 1970’s showed that 95% of bugs are caused by programmers. Of the rest, 2% fall on system software, 2% on other software, and 1% on hardware. 
Usually, it’s your fault.
These days though, compilers are built by people who aren’t exactly Ritchie, Stroustrup, Knuth, Wirth, and the like. Don’t get me wrong, compiler developers are still great guys. It just feels like modern core-tech code is missing the discipline of days gone by.
It’s a mess.
It’s an even bigger mess now that everyone is transpiling code from ES5 to ES6. In theory, this shouldn’t be a big deal. You take code from one version of the language, and you turn it into another version. Just a bit of back-porting and some syntax sugar converting.
In practice, it means there’s another compiler in your stack; two, if you’re using a dependency management system like Webpack as well.
So I Got Hit by a Compiler Bug…
Three weeks ago, I realized why Our App(tm) didn’t work on Safari. Some of my code was using ES6’s
Map, which Chrome supports, but Safari does not. I thought Babel would deal with that out of the box, but I was wrong.
Something to do with
Map being a semantic feature and Babel needing to use polyfills that implement
Map in pure ES5. This should be a simple configuration tweak.
What do you mean you’re still using technology that’s outdated by a whole entire month!? Get with the times, daddy-o!
You see, in theory, you’d want to avoid updating your build system for as long as possible. A lot of unknown unknowns can rear their ugly heads and bite you in the ass. But, I had a bug to fix and a system in production.
The upgrade path is simple: remove old packages, install new packages, tweak some configs. In the new system, you need 6 packages to do syntax transpiling and semantic polyfills; like this:
// dependencies in package.json "babel-core": "^6.3.15", "babel-loader": "^6.2.0", "babel-plugin-transform-runtime": "^6.3.13", "babel-polyfill": "^6.3.14", "babel-preset-es2015": "^6.3.13", "babel-runtime": "^6.3.13",
That’s a lot of packages to install for one piece of stuff, but hey, I’m sure that won’t be painful to update in two months at all…
You also need to add a
babel-polyfill entry to your Webpack config and enable the
es2015 plugins. That’s how Webpack knows to include the polyfills and to transpile ES6 syntax into ES5.
Easy enough. The code transpiled without error, the app loaded fine, and it even worked in Safari. Yass!
“Yo, when you check my PR, make sure you click around a bit,” I said to my coworker as I submitted a pull request. “It should be fine really. The only way something broke is if there was a bug in Babel 5 that we were unknowingly relying on to make our code work. I don’t foresee any issues, really. Compilers usually don’t have bugs.”
He didn’t click around. It wasn’t fine.
An hour after he closed my pull request, the intern started complaining on Slack:
“Hey, my code doesn’t work anymore. I don’t know what happened. I try to test this thing and it just doesn’t load.”
That’s… odd. That really shouldn’t be broken.
“Did you update from latest master?”
He did. The error was short and cryptic:
TypeError: (0 , _typeof3.default) is not a function
After jumping through the stack trace, we found the error deep inside the bowels of some sort of polyfill for
typeof. You know something weird is going on when
typeof throws a type error.
In this case, the error was caused by
typeof circularly trying to use itself in every instance our code used it in. Awesome.
Great. Now What?
So what do you do when you have to deploy tonight, the compiler is messing up your code, and a ticket for the bug has been open for three weeks?
Rolling back your change brings back the old bug. Fixing it yourself would take too long because it’s a hairy problem, and getting it released upstream is a pain-in-the-ass.
We were already using Lodash as an external dependency. That means we were adding a
<script></script> tag to load Lodash from public content delivery networks (CDN). This improves load times because users might already have it cached, and it improves our build times because Webpack and Babel have less code to process.
In this case, it also saved the day. Lodash has a ton of type helpers.
All it took to make the codebase ready for deploy was to change all instances of
typeof into Lodash’s type helpers—things like
As luck would have it, we’d only used
typeof in five places. My change only caused one bug that got through code review and into production!
typeof bug has since been fixed, by the way. Although the last comment on its discussion thread says “Uhh… I still get this when using babel-plugin-transform-runtime.” Guess we’re waiting for the version dependency to propagate through the world.
So What Went Wrong, Actually?
Uh, I got hit by a compiler bug in Babel 6. Weren’t you reading?
That’s more of a symptom than a root cause, though. Here’s the real sequence of events that led to the only time I found a compiler bug in 19 years of programming.
- Failure to test code in multiple browsers
- I thought we had polyfills; we didn’t.
- Lack of automated tests
- I relied on coworkers to “click around” to find problems.
- Merging bad things to master branch and not having a good rollback strategy
- If you break the master, you block the production deploy
- Haphazardly updating core parts of the build stack
- Babel throwing old versions out the window on upgrades
- Using bleeding-edge technologies
All in all, this was a very avoidable problem. But now I know, when you’re on the bleeding edge, it totally can be a compiler bug.
You should follow me on twitter, here.
Published at DZone with permission of Swizec Teller, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.