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!
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#extendis explicitly defined in C asSo because the C function
rb_obj_extendhas been “linked” (in quotes because I’m figuratively speaking, I don’t mean C linkage here) with the methodKernel#extendin the Ruby world, in theory you could overriderb_obj_extend‘s behaviour if you overrideKernel#extend.I would say given the following two conditions you could claim that you actually “overrode” a rb_* C function:
Now if you look at
rb_error_frozenit 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 whererb_error_frozenbubbles up to the “Ruby surface”. What I mean by that is that you could check all the places in the C sources whererb_error_frozenis 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_frozendirectly.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
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_frozenis called. The problem isrb_check_frozen– it’s called anywhere where an object is modified (as it ought to be) and again itself calls out torb_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.