I ran across a somewhat dangerous memory leak in an application the other day. We were using a grid system to plot the position of various elements, and the off-the-shelf system had a few critical issues with it.
First and foremost, an internal method created a few objects and bound DOM events on every chance of the internal grid map. Whenever the grid map changed, the script created new objects and bound them to the DOM as well.
Except it never unbound the old objects. The longer we ran the script, and the more changed we made to the grid, the more stale objects we had bound to the DOM. Our first order of business was actually invoking [cci].destroy()[/cci] methods presented on every element.
The second issue was more problematic.
Casting Errors
Central to our script was a set of nested loops that would iterate across every cell in the table-like grid and bind drop listeners to each. It was a simple grid that simply iterated over the columns in the outer loop, then in each column iterated through every row.
Assuming 12 columns and 16 rows, this would create 192 instances of the [cci]Coordinates[/cci] object, which internally tracked events. It’s a lot of objects, but not too impactful on memory.
However, the function that iterated across these objects did so based on passed-in counts of rows and columns. It worked under the assumption that both counts were passed as integers and began using them as such. A deeper investigation uncovered that they were being passed in as strings.
The condition within the foreach, [cci]$column_count += 1[/cci] and [cci]$row_count += 1[/cci], forced the index to append 1 rather than increment by 1. For example, [cci]”15″ += 1 = “151”[/cci] – The [cci]+=[/cci] operator does not cast the first element as an integer; it was casting the second operator as a string to match the first. This caused or script to create several thousand instances of the [cci]Coordinates[/cci] object, most of which represented inaccessible grid coordinates in the first place.
It also caused our web page memory to balloon beyond a reasonable handful of megabytes into the realm of 1.5+ gigabytes in no time. It made our application unusable, and was a significant source of frustration just before the holidays.
Resolution
As with many other issues plaguing web development, the resolution here involves properly sanitizing function inputs.
Our looping function worked under the assumption that the inputs were valid integers when, really, they were strings. Typically, JavaScript is smart enough to typecast when necessary – in this specific case, though, it was typecasting the wrong direction.
Instead of just hoping that JavaScript will cast for you, or that developers will only ever pass you good data, you should always validate the input to a function before working with said input.
In this case, passing both inputs through [cci]parseInt()[/cci] made sure we didn’t need to depend on type coercion and had a much more performant, memory-friendly application.