The Most Ridiculous Monkey Patches We’ve Seen

Danni Friedland
WalkMe Engineering
Published in
5 min readAug 15, 2016

“A monkey patch is a way for a program to extend or modify supporting system software locally.

The term monkey patch seems to have come from an earlier term, guerrilla patch, which referred to changing code sneakily — and possibly incompatibly with other such patches”

(Wikipedia)

Introduction

As a third-party JavaScript (JS) software provider, our code runs in the Wild Wide Web. We have to support websites that try to support Internet Explorer (IE) 6, shady WordPress themes, sites that were built in Dreamweaver and many other such first-world horrors.

This is indeed a foreign, and sometimes hostile, environment. Unknown browsers, unknown OSs, and unknown developers who race to create the most bizarre monkey patches you can think of. Bugs can be introduced indirectly in such a discreet way that you’ll question whether this whole Internet thing is really even worth it (spoiler alert: it is).

Here are some of the more strange (to put it mildly) modifications we’ve seen:

1. JavaScript Object Notation (JSON)

JSON is a universal standard, well-defined and vastly popular. Despite this, some developers still monkey patch it.

“How was JSON modified,” you ask? Let’s examine the following output:

JSON.stringify({a: undefined})
// {"a":undefined}

See anything strange? Yep, the correct output should be {}. As mentioned in the docs, the JSON.stringify() method normally omits undefined values.
Unfortunately, the above monkey patched version did not do so. Faulty JSON was sent to our servers, our JSON.parse() failed and had the starring role in that day's log's high-scores.

But why? Probably a sloppy attempt to support older browsers. IE 6 strikes again, from the (well-deserved) grave.

2. Function.prototype.bind( )

Function.prototype.bind() is an important method, fundamental to JS's functional nature. An unnamed WordPress theme developer took it upon himself to re-implement it:

Function.prototype.bind = function(scope) {
var self = this;
return function() {
return self.apply(scope, arguments);
};
}

Can you spot the bug? Well, this implementation lacks an important feature of bind: currying. This will not work:

function add(a, b) {
return a + b;
}
var add10 = add.bind(null, 10);
add10(1); // Should be 11, returns NaN

3. Array.prototype.map( )

Who doesn't love Array.prototype.map()? It’s an elegant functional programming technique. It should always run... right? This time, the culprit will be named, as it's none-other than Prototype.js, albeit a pretty old version of it.

Let's look at their code:

// map made into an alias of collect later
collect: function(iterator, context) {
iterator = iterator || Prototype.K;
var results = [];
this.each(function(value, index) {
results.push(iterator.call(context, value, index));
});
return results;
}

Professional, just as we’d expect. They even added Array.prototype.each for us. Unfortunately, an important property of map was lost in translation. We can no longer use this nifty trick:

Array.prototype.map.call(arguments, fn);

In fact, all the Array-like object tricks flew right out the window.

Prototype.js have long since changed the implementation. But in the Wild Wide Web, much like vampires who drink red wine, your sins live forever.

Protection

How should a responsible third party developer respond to such shenanigans? You could do nothing and just declare you don't support or handle such cases. This is not the path we'll suggest that you take. It is possible to protect your code, at least to some degree, from the harsh reality of the Internet.

1. Immediately Invoked Function Expressions (IIFE)

A common misconception is that it’s a best practice to enclose your code within an IIFE. The function will receive a reference to the global variables you want to protect.

For example, in order to ensure you have the original window, jQuery, and undefined variables, you can do the following:

function(window, $, undefined) {
// your code
}(window, $)

Now your code will continue working even if the user does something like:

window.$ = { ajax: function() {} };

This method provides a very partial protection. Let's say that after your code is loaded, the user adds the following code:

$.ajax = function() {}

Since you only kept a reference to the jQuery variable, you are still vulnerable to modification on the object itself.

In reality, this method has other benefits, such as shortening the list of scopes JS needs to look in to find your variable, or enabling JS obfuscators to rename common variables such as window or document.

2. More IIFE

We can utilize a similar construct to UMDs module pattern. Here we clone the objects we want to protect, and then pass them to our IIFE:

function(factory) {
var $ = clone($);
factory(window, $)
}(function(window, $){
// your code
})

Of course, we have to ask ourselves, “where is the limit?” Should we just clone the window and be done with it? Should we take the burden of managing a list of all the language functions we use and clone them on an individual basis?

Alas, even this method would not guarantee native function integrity, as the user might override them before loading our code. It is, in fact, impossible to create such security.

Detecting a monkey patched method?

Wouldn't it be nice to just be able to test whether a function had been monkey patched? If we could do that, we'd at least be able to fail gracefully.

Unfortunately, JS doesn't make our life this easy. However, a property that might come in handy is the output of the toString()
method for native functions:

Function.prototype.bind.toString();
// function bind() { [native code] }

Utilizing this, we can create a monkey patch detection method:

function isMonkeyPatched(fn) {
return !Function.prototype.toString.call(fn).match(/native code/);
}

Unfortunately, this will only work for native functions, and will fail if our beloved users monkey patch toString().

In conclusion

JS, as wonderful a language as it is, allows everyone to override almost everything. We have encountered some very bizarre bugs that resulted from strange monkey patches. Even though it's always nice to find out that a bug was not caused by your code, you need to ask yourself, “what's next?” Do you approach the client and tell them their code breaks our code? Do you just handle it yourself? Understanding the nature and predominance of monkey patching in the wild is the first step towards answering this question for yourselves.

--

--

Danni Friedland
WalkMe Engineering

Currently VP Insights @ Walkme LTD. Interested at processes from the macro to the micro