views:

78

answers:

3

Hi all,

I'm looking for examples of why it's not a good idea to extend base classes in ruby. I need to show some people why it's a weapon to be wielded carefully.

Any horror stories you can share?

+1  A: 

One obvious pitfall would be name collisions - if two or more packages choose the same name for a method that behaves differently.

gnibbler
+2  A: 

The Trifecta of FAIL; or, how to patch Rails 2.0 for Ruby 1.8.7 has an example of Rails (which is a large, well-scrutinized project) causing problems because they monkeypatched String to add the method chars.

Andrew Grimm
+4  A: 

There was a pretty famous example of monkey-patching going horribly wrong about 2.5 years ago in Rubinius.

The interesting thing about this case is that both the offending code and the victim were highly visible and highly unusual. Usually, the offender is some piece of code written by some PHP script kiddy who got drunk on his 1337 metaprogramming h4X0r skillz. And the failure mode is a simple ArgumentError exception, because the original method and the monkeypatch have different arity.

However, in this case, the offender was a library in the stdlib (mathn) and the failure mode was the Rubinius VM completely blowing up.

So, what happened? Well, mathn monkeypatches the Fixnum class and changes how Fixnum arithmetic works. In particular, it changes both the results and the types of several core methods. E.g.:

r = 4/3  # => 1
r.class  # => Fixnum

require 'mathn'

r = 4/3  # => (4/3)
r.class  # => Rational

The problem is of course that in Rubinius, the entire Ruby compiler, the entire Ruby kernel, large parts of the Ruby core library, some parts of the Rubinius VM and other parts of the Rubinius infrastructure, are all written in Ruby. And of course, all of those use Fixnum arithmetic all over the place.

The Hash class is written in Ruby and it uses Fixnum arithmetic to compute the size of the hash buckets, compute the hash function and so on. Array is written in Ruby and needs to compute element sizes and array lengths. The FFI library is written in Ruby and needs to compute memory addresses(!) and structure sizes. Many parts of Rubinius assume that they can do some Fixnum arithmetic and then pass the result to some C function as a pointer or int.

And since Ruby doesn't support any kind of selector namespacing or class boxing or similar (although something like that is planned for Ruby 2.0), as soon as some random user code requires the mathn library, all of those pieces just spectacularly explode, because all of a sudden, the result of a Fixnum operation is no longer a Fixnum (which is basically identical to a machine int and can be passed around as such), but a Rational (which is a full-fledged Ruby object).

Basically, what would happen, is that some code would require 'mathn' (or you would type that into IRb), and immediately the VM would just die.

The solution, in this case, was the safe math plugin for the compiler: when the compiler detects that it is compiling the kernel or other core parts of Rubinius, it automatically rewrites calls to Fixnum methods into calls to private immutable copies of those methods. [Note: I think in current versions of Rubinius, the problem is solved in a different way.]

Jörg W Mittag
Yeah that is definitely scary. And it sounds like a complete nightmare to track down.
zaius