Hacker News new | past | comments | ask | show | jobs | submit login
Monkey Patching in Go (bouk.co)
82 points by Sirupsen on March 30, 2015 | hide | past | favorite | 22 comments

This is awful, dangerous, and compiler designers and writers will yell at you. Well done :)

I've always wanted a way to monkey-patch otherwise statically and well-bound code, but only for testing. In C, it's fairly easy to set up preprocessor rules to do so, though also litters your primary code with test specific stuff. I have mostly focused on how to integrate this into compiler logic; Building some functionality to replace specified symbols in the build phase. Simply replacing the function pointers in the runtime is brilliant, if more than a little hacky and prone to problems.

I look forward to playing around with this.

This is not monkey patching. This is the good old function/api "hooking" that's pretty common in the compiled language world: http://en.wikipedia.org/wiki/Hooking

Monkey patching is a less invasive version of hooking used in dynamic languages. Bottom line: if you are dealing with assembly it's no longer monkey patching :-)

There's not really anything equivalent in the Go world right now so I chose to just adopt the Ruby/Python term :)

It kind fits in with what Wikipedia describes as monkey patching http://en.wikipedia.org/wiki/Monkey_patch

Except the wiki page about monkey patching talks about it in the context of dynamic languages :-) It doesn't talk about C, C++ or other compiled languages.

Definitely nice work!

Dynamic and compiled are not mutually exclusive. Objective-C is both a dynamic and compiled (to machine code) language that supports monkey patching. It supports extending classes with new methods and overriding existing methods in existing classes through its category feature.

This does have an unfortunate side effect though. Because Objective-C is a) compiled directly to machine code b) has no way of marking methods final and c) supports monkey patching, essentially no methods can be inlined by the compiler.

Of course, dynamic and compiled are not mutually exclusive. Go is not like Objective-C though and it doesn't support "Categories" or "extension methods" :)

I feel like I'm pretty familiar with "monkey patching" as practiced in Rubyland, and I'm a systems C programmer from the 90s. Can you help me understand the distinction you're drawing here? I don't have an understanding of "monkey patching" that involves how "invasive" it is; in fact, a repeated complaint about Ruby is how uninvasive it makes monkey patches, since the very concept of monkey patching breaks assumptions other programmers might rely on it.

I think the important distinction is that Ruby/Python monkey-patching work within the defined semantics of the language, whereas this is targeting implementation-specific details of one particular runtime.

At the end of the day however, it's still patching code you don't own. Doesn't the term strongly imply modifications outside the scope of the author's intentions? Even in ruby and python, monkey patching is rarely the documented means to modify a library's behavior.

Yes, monkey patching is pretty uninvasive. It's a better way to describe it. It's a "feature" supported by a dynamic language. Still, unexpected things can happen if something is patched multiple times though it's easy to track and fix :)

Monkey patching ends and hooking begins when you go outside of what's provided by the language digging into assembly and byte code.

This does not work for any non-trivial function because of missing stack map for the GC and stack copier.

So, this is a step towards implementing plugins for go right? It seems to me like this could be used to implement dynamic code loading.

Dynamic code loading is what makes C#/.net orm's fast (see Dapper, Destrier, PetaPOCO etc.) Basically they let you use an IL generator to emit a bunch of IL operations, then compile them to a delegate. This is useful specifically for creating the methods that take a data reader and turn discrete columns into fields on a database mapped object.

As soon as we can do this with Golang the orms we use will get much faster.

No, this is just a hack that is probably broken because the runtime doesn't know about the patched functions. The Go team are working on proper loadable modules.

How is it broken?

Well, in the future the go runtime might require more detailed stack info other things to enable future things like concurrent garbage collection. Also, different implementations of Go have different requirements and calling conventions.

This is admirable work in the pursuit of evil.

Is this safe for such short functions? What if the patch takes more space than the original code?

All functions take up at least 16 bytes, for alignment reasons I assume. The space that isn't used just gets filled with zeroes.

The patch is 12 bytes

On all platforms?

Could this be expanded to hot-swap code as you write it?

Applications are open for YC Winter 2022

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact