I was upgrading a Rails 4.1 app from Ruby 2.2.3 to 2.3.1 and got an interesting error; from a debugger session:
Ben Sullivan has a nice writeup of some oddities around Rails 4.1's ActiveSupport::Duration and how it uses BasicObject
(and David Stostik talks about it more here), so I won't repeat that here. The fix was simple, just:
But, it's still a puzzling difference. Taking Rails out of the equation, you can see the difference between 2.2.3 and 2.3.0:
It's pretty straightforward to trace this up to a certain point. In random.c InitVM_Random
defines rand
as a global Ruby function that maps to the C function rb_f_rand
. That calls rand_range
, which calls range_values
, which calls rb_range_values
which is defined in range.c. Then there are some duck-typing checks. There's a call to rb_respond_to
which passes the argument (in this case, an instance of BasicObject
) to rb_obj_respond_to
to see if it responds to the message begin
. And, surprisingly, it gets back a result of true! The same thing happens to the check for the existence of an end
method. If either of those returned false, rb_range_values
would have short-circuited with an early return. But they don't, so it doesn't, and so the runtime attempts to invoke begin
on the object and a NoMethodError
is raised.
rb_respond_to
is defined in vm_method.c, and it delegates to rb_obj_respond_to
which calls to vm_respond_to
, which calls to method_entry_get
see if that method is defined. That's the part that puzzled me - this snippet from vm_method.c:
Seems like that should return -1
, not TRUE
, right?
As I was writing this up, poking around this code led me to git blame vm_method.c - and what do you know, it was just fixed on trunk! Jeff C reported this bug two months ago, Dan Barry has a nice comment there on what's going on, and then Nobu just fixed this yesterday with this changeset. Mystery solved!
Incidentally, here's another manifestation, this one from from Dan Barry's comment on the bug - same issue but different method, showing the difference between 2.2.3 and 2.3.0: