Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I over-ride Ruby methods written in C?

Tags:

c

ruby

overriding

Is it possible to over-ride methods that are part of Ruby itself, such as rb_error_frozen, that are written in C, with Ruby code?

Background: I'm wondering if it's possible to make Ruby merely log a warning, rather than raise an exception, when a frozen object is modified. That way, I can log a variety of state modifications, rather than stopping when the first one occurs.

I'm primarily thinking of doing this with YARV, but I could use another implementation if that made it easier.

And yes, it's a whyday project! Don't try this in a production environment!

like image 569
Andrew Grimm Avatar asked Aug 21 '11 13:08

Andrew Grimm


People also ask

Does ruby support overriding?

Ruby does not support method overloading Method overloading is a feature of statically typed language in which binding of methods takes place during compile time. But Ruby being a dynamically typed language, it does not support static binding at all.

How to override class in ruby?

Override means two methods having same name but doing different tasks. It means that one of the methods overrides another method. If there is any method in the superclass and a method with the same name in its subclass, then by executing these methods, method of the corresponding class will be executed.

What does it mean when a method ends with in Ruby?

Ruby doesn't treat the ! as a special character at the end of a method name. By convention, methods ending in ! have some sort of side-effect or other issue that the method author is trying to draw attention to.


2 Answers

I can only speak for MRI/YARV, but I'll give it a try. You can only override functions that originate in C in Ruby if the C function has been explicitly defined as a method on a Ruby object. For example, Kernel#extend is explicitly defined in C as

rb_define_method(rb_mKernel, "extend", rb_obj_extend, -1);

So because the C function rb_obj_extend has been "linked" (in quotes because I'm figuratively speaking, I don't mean C linkage here) with the method Kernel#extend in the Ruby world, in theory you could override rb_obj_extend's behaviour if you override Kernel#extend.

I would say given the following two conditions you could claim that you actually "overrode" a rb_* C function:

  • the rb_* C function has been "linked" with some Ruby object, so we have a handle in Ruby world that serves as a hook that we can override
  • the given rb_* method is only used in this one spot for exactly this purpose, it's reused nowhere else

Now if you look at rb_error_frozen it fulfills neither of these two conditions. It's a helper in the C implementation, meaning it's called from several places. And it has not been explicitly "linked" with any Ruby object, so you have no hook where you could override it.

Not all is lost, though. You can't directly override rb_error_frozen, but what you could still try is to override all the Ruby methods where rb_error_frozen bubbles up to the "Ruby surface". What I mean by that is that you could check all the places in the C sources where rb_error_frozen is used and from these places try to find each and every Ruby method that could trigger these bits of code. If this is a closed set, you could simply override all of these methods in order to "de-facto-override" rb_error_frozen's behaviour.

This is only a patchwork solution, however. All your hard work is lost should somebody decide to write another C extension where they again call rb_error_frozen directly.

So long story short: You can only override a C function if it has been explicitly defined as the implementation of some method of a Ruby object, e.g. as in

rb_define_method(rb_cString, "gsub", rb_str_gsub, -1);

where you can assume that it will only ever be used for that purpose only. But even then you're not a 100% safe, somebody could still decide to reuse that function in some other part of C code.


Edit: You said you'd like Ruby only to warn instead of raise if a frozen object was modified. I just went through the sources to see if you could override all of the places where rb_error_frozen is called. The problem is rb_check_frozen - it's called anywhere where an object is modified (as it ought to be) and again itself calls out to rb_error_frozen. This mechanism is deeply rooted in the C internals and not published at the Ruby surface everywhere, so there's no way to override the "raising behaviour" or at least none that would not require significant effort. If you think about it for a minute that is actually a good thing. If it were possible to simply override the behaviour then this could actually be seen as a security flaw in the Ruby implementation. Freezing an object should guarantee you that it stays unmodifiable no matter what.

like image 147
emboss Avatar answered Sep 28 '22 07:09

emboss


Yes, of course you can override Ruby methods that are implemented in C (or Java or C# or C++ or Objective-C or ECMAScript or whatever).

Since Ruby is an object-oriented language, pretty much the only thing you can do in Ruby is to either override or create new methods, and since on the two most widely-used Ruby implementations (MRI and YARV) all methods are implemented in C and none in Ruby, you simply wouldn't be able to do anything if you you couldn't override methods written in C.

However, there is a big problem: rb_error_frozen isn't a Ruby method. It's a C function.

like image 21
Jörg W Mittag Avatar answered Sep 28 '22 06:09

Jörg W Mittag