Have you ever wanted to redefine a method, chaining it to the original method, but make sure that the original method was uncallable? No? Well yea, most people probably haven’t. But it’s an interesting idea and I actually have a somewhat legitimate use case for it, so I’m going to talk about it. Please note, the below are examples, not what I actually used it for.
The usual way to chain a method is:
class String alias_method :original_upcase, :upcase def upcase s = original_upcase "upcased!: #{s}" end end
But if someone wanted to call the original upcase all they would need to do is call original_upcase
. Maybe you think you could remove_method :original_upcase
. But no, that would break the new upcase when it tries to call the original.
Luckily there is a way to do this with lambdas, method objects, and enclosed local variables.
class String m = instance_method(:upcase) define_method :upcase do s = m.bind(self).call "upcased!: #{s}" end end
We have now overwritten the original upcase method without having to first alias it. The original method only exists in the local variable m
, which was enclosed in the block sent to define_method
. After the end
of class String
that local variable is now out of scope and effectively non-existent. It only exists in the block, but there is no way to extract the value of it from the block without being able to modify the block.
Of course the method object is still in existence, which means it could be found with
methods = [] ObjectSpace.each_object(UnboundMethod) { |m| methods << m }
This is averted by simply removing the ObjectSpace constant:
class Object remove_const :ObjectSpace end
Update: Pat Maddox pointed out that you could get access to the original Method objects through modification of the Method or UnboundMethod classes. We can prevent this by freezing both classes so that no further modification of them is possible. This includes adding, removing, redefining methods, etc.
[Method, UnboundMethod].each{|klass| klass.freeze }
So there you have it, secure alias method chaining. Or…. can anyone figure out a way to access the original method without using ObjectSpace (and without using C extensions of course)?
Update: Ok this has already been pwned by Maddox. If you redefine Method#call
you can get access to the method object. So to keep things secure we’d have to prevent someone from modifying the methods of the Method
class. This might be possible using something like http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby. I’m not sure if that will prevent all tampering attempts though, I’ll have to look into this.