loading...
Cover image for Debugging Challenge: The cursed default clause

Debugging Challenge: The cursed default clause

sebastianstamm profile image Sebastian Stamm ・3 min read

An evil witch cursed my switch statement so that no matter what I put in, it always executed the default clause instead of the case clauses.

This is a problem that I encountered in real life and is still my favorite bug. Here is a minimal setup you can play around with: Check it out on codesandbox.io

Can you find out what is going on? I had an enlightening aha-moment when I figured it out, so I encourage you to have a look! The rest of this post describes the problem and why it behaves this way. If you want to find it out yourself, stop reading here.

What is going on?

function handleError(error) {
  switch (error) {
    case 404:
      console.log("Document not found");
      break;

    case 500:
      console.log("Server Error");
      break;

    default:
      const error = {
        msg: "Got an unknown error code",
        timestamp: Date.now()
      };
      console.log("Unknown Error Incident", error);
  }
}

handleError(404); // should log "Document not found", but logs “Unknown Error Incident”

No matter what you call the function with, it always executed the default clause. Which is weird because there are cases that cover the inputs for the other values, so in those cases it should not go to the default clause.

You will notice however, that the code itself is fine. If you copy it into your browsers console and execute it, you will see that it goes into the case clause if necessary and only uses the default if none of the case clauses match.

The problem is not the code that is written, but the code that is executed.

Most projects use something called a transpiler. A transpiler takes Javascript code and compiles it to other Javascript code. This is so you can write fancy modern Javascript with all the cool new features and still have it run in Internet Explorer.

Which is a really awesome concept, but it also adds another layer of abstraction that can cause subtle bugs in your code. This is exactly what happened here. This is the code that is actually executed after transpilation:

function handleError(error) {
  switch (_error) {
    case 404:
      console.log("Document not found");
      break;

    case 500:
      console.log("Server Error");
      break;

    default:
      var _error = {
        msg: "Got an unknown error code",
        timestamp: Date.now()
      };
      console.log("Unknown Error Incident", _error);
  }
}

handleError(404); // should log "Document not found", but logs “Unknown Error Incident”

Looking at this code it’s instantly clear that this can’t work as the switch uses a variable that is not initialized yet. This is an actual bug in the transpiler: https://github.com/babel/babel/issues/5378

If we rename the error variable in the default clause, everything works as it should even in the transpiled code.

How to debug this

Fortunately bugs like this are quite rare. And now that you are aware that transpilers can cause such problems you are already one step closer to identify and fix them.

One simple step you can take to run the untranspiled code in the browser console. Most of the time you need to simplify your code to isolate it from your business application, but constructing a minimal test case is a good practice anyway.

The other way is to inspect the transpiled source code. In the chrome console you can click on the “sourcemapped from” link to see what is actually executed.

How to get to the transpiled code from the Chrome Console

Did you ever encounter a bug that made you scratch your head before discovering a surprising solution? I’d love to read about them! Also, since this is my first blog post, if you have any thoughts or suggestions on how I could improve, please share them with me, too!

Discussion

pic
Editor guide
Collapse
willsmart profile image
willsmart

Love it. That's really counterintuitive but makes sense.

As a carry over from working in c and c++ I've always enclosed any case blocks that define variables in curlies, since it avoids redeclaring variables if you have two cases declaring under one name (a similar issue to this one).
So, my code would be:

function handleError(error) {
  switch (error) {
    case 404:
      console.log("Document not found");
      break;

    case 500:
      console.log("Server Error");
      break;

    default:
      {
        const error = {
          msg: "Got an unknown error code",
          timestamp: Date.now()
        };
        console.log("Unknown Error Incident", error);
      }
      break;
  }
}

handleError(404); // should log "Document not found", now it does
Collapse
brunooliveira profile image
Bruno Oliveira

Damn, awesome job!! I mean, not as a JS expert, but, as a fellow programmer, I can only imagine the joy when finding this out!!