Post

How to locate the source of a Ruby method

UPDATE Jun 10, 2024: Modify the article to account for Ruby 3.3 adding default location for eval’d methods.


“Wait, where the hell is this method coming from??”

One of Ruby’s strengths is how highly dynamic it is. This makes it very expressive but it also means that it can often be hard to figure out where a method that you see on an object has come from. Full featured Ruby editors like RubyMine and various plugins for other editors based on gems like Solargraph have advanced in recent years and have excellent ability to find the definition of a method.

However, Ruby is so dynamic that there are cases where it is impossible to determine reliably without actually running the code. Static analysis having its limits is the cost of super high dynamism and you will eventually find yourself in a ruby REPL wondering: “Where is this method actually defined?”

There are multiple cases to consider, let’s dig in.

The easy case: Regular Ruby method

Assume you have a source file defined as follows:

1
2
3
4
5
6
# file: foo.rb
class Foo
  def bar
    "BAR!"
  end
end

Ruby has the answer ready in the form of source_location which will very helpfully tell you that the method source is in file “foo.rb” at line 3:

1
2
3
4
3.2.2 :001 > require_relative "foo"
 => true
3.2.2 :002 > Foo.new.method(:bar).source_location
 => ["~/foo.rb", 3]

The super special case

Calling the super method from a method looks suspiciously like a method call:

1
2
3
4
5
class FooChild < Foo
  def bar
    super()
  end
end

But if you try to get its location directly with method(:super) you’ll get a NameError: undefined method 'super'. That is because super is a keyword that is resolved by the VM to the actual super method. Instead you want to use Method#super_method and apply the same approach we just learned to get its location: FooChild.new.method(:bar).super_method.source_location.

Bonus: locating constants.

Since Ruby 2.7 we have another powerful method at our disposal: const_source_location which allows us to do the same for constants:

1
2
3.2.2 :002 > Object.const_source_location(:Foo)
 => ["~/foo.rb", 2]

Pry

If you are using Pry as your REPL instead of the default IRB, it’s even easier. Pry has the show-source (aliased as $ ) command which is even more helpful and automatically works for both constants and methods:

1
2
3
4
5
6
7
8
9
10
[2] pry(main)> show-source Foo.new.bar

From: ~/foo.rb @ line 3:
Owner: Foo
Visibility: public
Number of lines: 3

def bar
  "BAR!"
end

Side note, there’s also a companion show-doc (aliased as ?) command that shows the YARD documentation for the method.

The hard case: Dynamically defined Ruby method

Let’s consider a case where we dynamically define some methods but still use regular Ruby code defined in a block:

1
2
3
4
5
6
7
8
9
# file: dynamic.rb
class Dynamic
end

%i[foo bar].each do |name|
  Dynamic.define_method(name) do
    "#{name.upcase}!"
  end
end

Both source_location and Pry’s show-source will point to the place where the method is defined, in this case the line number 6, the one calling define_method. Easy.

Since Ruby 3.3., if you call one of the eval methods with a string, the caller location be shown in the location string:

1
2
3
4
# in dynamic.rb
Dynamic.class_eval "def evald; 'EVALD!'; end"
# later
Dynamic.new.method(:evald).source_location # => returns [(eval at dynamic.rb:1), 1]

However, this is just a default and eval allows you to pass what you consider the correct location of the source. I.e. if you do the following:

1
Dynamic.class_eval "def evald_with_source; 'EVALD!'; end", "intended_source_file.rb", 42

Then source_location will return the custom, and presumably correct, location you specified.

To help your future self, get into the habit of specifying the file and line number when doing meta-programming with eval and friends. :)

Meta-programming with eval is not uncommon in ruby gems so if you do find yourself in the very unfortunate case of having to find the source of an eval’d method your best bet might be disassembling the compiled method and looking for constants or simple operations for which you can guess the source code snippet and search for it:

1
puts RubyVM::InstructionSequence.disasm(Dynamic.new.method(:evald))

The “beyond Ruby” case: C code

Let’s go meta, just a bit: What is the source location of the source_location method?

1
2
3.2.2 :003 > Foo.new.method(:bar).method(:source_location).source_location
 => nil

What? nil? What’s happening is that source_location is defined in the C source code of MRI. Anytime you see source_location return nil you know that the method was not defined in Ruby.

If you are using Pry you can install the pry-doc gem and it will allow you to reveal the source location of MRI internal methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[3] pry(main)> require "pry-doc"
=> true
[4] pry(main)> $ Foo.new.method(:bar).source_location

From: proc.c (C Method):
Owner: Method
Visibility: public
Signature: source_location()
Number of lines: 5

VALUE
rb_method_location(VALUE method)
{
    return method_def_location(rb_method_def(method));
}

But what if you can’t use Pry, or, you need to find the source of a method in a gem’s C-extension? pry-doc does a lot of work to locate the gems, actually using artifacts of parsing the C-code, but in most cases you can find it relatively quickly yourself in the C-extension source if you know what to look for.

First you need to know just a little bit about how C-extensions define the method. There’s two parts: First, there is the C method defined in C-code. However that name gets lost in the compilation and actually exposing the method to the Ruby VM involves a call to one of several methods, with probably the most common being rb_define_method and that method needs to receive a string with the ruby name of the method. You can exploit that by searching the MRI or the relevant gem’s source for the ruby name, surrounded by quotations (don’t forget to escape them). Like this search on ruby language github repo. This search returns a few occurrences, but let’s focus on these two:

rb_define_method(rb_cProc, "source_location", rb_proc_location, 0);
//...
rb_define_method(rb_cMethod, "source_location", rb_method_location, 0);

Notice the parameter after the string: rb_proc_location and rb_method_location. Those are actual names of C methods. Using some clever deduction skills you can guess that first one is for the source_location method on a Proc object and second on the Method object. Searching for those will usually reveal just a few occurrences with one obviously being the definition of the method.

If you’ve never worked with C it might feel intimidating to read C-code but usually you don’t actually have to fully understand it. You’re probably looking for an answer to a specific question about what the method actually does. And for that you can often correctly guess what the C-code is doing and draw an informed guess as to how it maps back to Ruby code. In the end you’ll verify the conclusion from Ruby land anyway.

Do it a few times and you’ll notice you’re getting better at it every time!

This post is licensed under CC BY 4.0 by the author.