Skip to content

Instantly share code, notes, and snippets.

@latentflip
Created December 7, 2012 14:41
Show Gist options
  • Save latentflip/4233671 to your computer and use it in GitHub Desktop.
Save latentflip/4233671 to your computer and use it in GitHub Desktop.
Ruby Pop Quiz
class Foo < BasicObject
def method_missing(method, *args, &block)
puts "You called #{method}"
end
end
Foo.new.hi
@latentflip
Copy link
Author

For the record, this will raise a stack overflow error, eventually.

Why? 2 reasons.

  1. The implicit receiver is always self.
  2. Ruby has very few keywords.

This means that if a message is not a keyword, or a global, it will be sent to self.

Here are ruby's keywords:

BEGIN   END   __ENCODING__   __END__   __FILE__   __LINE__   alias   and   
begin   break   case   class   def   defined?   do   else   elsif   end   ensure   false   
for   if   in   module   next   nil   not   or   redo   rescue   retry   return   self   super  
 then   true   undef   unless   until   when   while   yield

Note the suspicious lack of puts. This means that when we use puts we are not calling a ruby keyword, but we are sending a message puts to the implicit receiver (self.puts).

So where does puts come from? Kernel: http://ruby-doc.org/core-1.9.3/Kernel.html#method-i-puts

Fortunately Kernel is mixed into Object (http://ruby-doc.org/core-1.9.3/Object.html), which means it's available to any object in our system which inherits from Object, which is mostly all of them. So we can normally use puts without thinking about it.

But here we don't inherit from Object, we inherit from BasicObject, which is a ruby 1.9 addition, which gives us a very empty class, with only the bare minimum of methods:

BasicObject.instance_methods #=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

So there is no puts on our object. So calling puts on our object will call method_missing which calls puts and so on, until ruby gives up.

Fun huh?

@latentflip
Copy link
Author

The way to avoid this is to use the global $stdout.puts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment