Debug session: Javascript, toString, object, maps e outras porcarias
Fun debugging session that happened at $WORK.
I found that a component, published to npm was crashingwhen giving a certain input.
Since it was minified, it was hard to see where the error was. I added the sourcemap. It happened in a loop, a certain property which was expected to be an array, ended up being a Number:
| |
Firefox error message was kinda confusing:
| |
Chrome’s was better:
| |
That’s the thing with TypeScript: types are not real.

Anyway, after some debugging I found that it happened when the string toString is used. I handcrafted a minimum reproducible example, which helped me debug. Debuggers are cool, but Conditional Breakpoints often let me down.
To give some context, the code iterates a tree and merges nodes’ values with the same name. It’s a feature for sandwich viewing if you are interested.
What I found curious is that when I logged the node, it told me it was a function with properties!
| |
This is something I kinda forgot about, but why wouldn’t be possible?
| |
Back in the day we used to write constructors using functions:
| |
And of course, ES6 classes have some syntax sugar over this implementation (but not only that!).
Anyway, what was happening is that we created a map-like object. And then to not initialize twice, we did a check for the presence of an existing item:
| |
However, since toString always exist in an object, it wasn’t being initialized correctly!
Then it would spiral in some crazy madness I won’t go deep about.
The solution was to just replace the map-like object for a real Map:
A Map does not contain any keys by default. It only contains what is explicitly put into it.
An Object has a prototype, so it contains default keys that could collide with your own keys if you’re not careful.
I didn’t explain where the properties of toString were coming form, to figure out I used a handy Proxy:
| |
Whenever toString is set (ie overridden), the debugger is called.
With that I managed to find the offending code:
| |
So if name is for example, toString, hash[name] returns the function from the prototype chain, ie from Object.prototype.toString, which attributes are added, leaking to every single object.