Skip to content

Instantly share code, notes, and snippets.

@nashby

nashby/blog.md Secret

Created Apr 5, 2018
Embed
What would you like to do?

Fixing Kernel#singleton_method bug in Ruby.

Few days ago when I was browsing through StackOverflow I came across this question about weird behaviour of Kernel#singleton_method such as if you call Kernel#singleton_methods method on ActiveSupport::Deprecation class you would get you list of signleton methods (no surprise here) but when you try to get that singleton method with Kernel#singleton_method Ruby would throw NameError error:

https://gist.github.com/b2990f5a3bcc82d45e16bd991270ff22

At first, I thought it's ActiveSupport doing something fishy. And while I was looking through AS codebase this answer appeared. @michaelj discovered that it's possible to reproduce this bug without ActiveSupport at all. All you needed to do is to append any module to a singleton class:

https://gist.github.com/81e3ab989d2548032e54b140249dffcb

So it was something wrong with Module#append.

I started looking for some bugs with Module#append on Ruby's bugtracker. All I could find related was this bug with Object#methods and Module#prepend. So what I needed to do is to check how they fixed it and do something similar with Kernel#singleton_method.

DISCLAIMER: I'm not that good with Ruby internals so next part of the post might have some incorrect statements.

Main thing I learned from Object#methods fix was RCLASS_ORIGIN macro. This macro is used to get origin class of the passed class/module. And as I discovered Module#append makes a copy of a target class internally so if you need to access original one you can use that macro. Honestly, I don't really understand why you can't access singleton method but I got the idea.

That's how rb_obj_singleton_method looked before the fix:

https://gist.github.com/539ab27cec3884b9fd602be5144b05f1

as you can see the klass was retrieved by rb_singleton_class_get function which returns class's copy if it was already appended by some module. That means all I had to do is to apply RCLASS_ORIGIN on that class. And it did the trick. You can find the whole patch in this issue.

And may Ruby be with you.

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